Initial commit

This commit is contained in:
libertyspy
2026-02-08 16:01:21 +08:00
parent 9ded6b757c
commit c9d5c38b52
11 changed files with 137 additions and 101 deletions

View File

@@ -31,7 +31,7 @@
<script lang="ts"> <script lang="ts">
import {defineComponent} from 'vue'; import {defineComponent} from 'vue';
import { CheckOutlined, RollbackOutlined } from '@ant-design/icons-vue'; import { CheckOutlined, RollbackOutlined } from '@ant-design/icons-vue';
import { elementProps } from './builder/props'; import {elementProps} from './builder/props'
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -43,19 +43,19 @@ export default defineComponent({
setup(props, ctx) { setup(props, ctx) {
const handleSave = ()=> { const handleSave = ()=> {
ctx.emit('save'); ctx.emit('save')
}; }
const goback = ()=> { const goback = ()=> {
}; }
return { return {
graph: props.graph, graph: props.graph,
node: props.node, node: props.node,
handleSave, handleSave,
goback, goback,
}; }
}, },
}); })
</script> </script>

View File

@@ -12,7 +12,7 @@ import type { NodeTemplateQuery, NodeTemplatesResponse, TreeModelDetailsResponse
import type { ApiPaginationQuery, BasicResponse } from '@/types'; import type { ApiPaginationQuery, BasicResponse } from '@/types';
const req = HttpRequestClient.create<BasicResponse>({ const req = HttpRequestClient.create<BasicResponse>({
baseURL: '/api', baseURL: '/bhtree/api',
}); });
export const findTemplatesByQuery = (query: Partial<NodeTemplateQuery>): Promise<NodeTemplatesResponse> => { export const findTemplatesByQuery = (query: Partial<NodeTemplateQuery>): Promise<NodeTemplatesResponse> => {
@@ -20,7 +20,7 @@ export const findTemplatesByQuery = (query: Partial<NodeTemplateQuery>): Promise
}; };
export const findTreesByQuery = (query: Partial<ApiPaginationQuery>): Promise<TreeModelsResponse> => { export const findTreesByQuery = (query: Partial<ApiPaginationQuery>): Promise<TreeModelsResponse> => {
return req.get<TreeModelsResponse>('/system/behaviortree/list', query); return req.postJson<TreeModelsResponse>('/behavior-trees', query);
}; };
export const findOneTreeById = (id: number): Promise<TreeModelDetailsResponse> => { export const findOneTreeById = (id: number): Promise<TreeModelDetailsResponse> => {

View File

@@ -98,12 +98,12 @@ export default defineComponent({
}; };
onMounted(() => { onMounted(() => {
console.info('node onMounted'); console.info('node onMounted')
_props.node?.on('change:data', handleDataChange); _props.node?.on('change:data', handleDataChange);
}); });
onUnmounted(() => { onUnmounted(() => {
console.info('node onUnmounted'); console.info('node onUnmounted')
_props.node?.off('change:data', handleDataChange); _props.node?.off('change:data', handleDataChange);
}); });
@@ -121,7 +121,6 @@ export default defineComponent({
border: 1px dashed #7a6986; border: 1px dashed #7a6986;
box-shadow: 2px 2px 5px #000000; box-shadow: 2px 2px 5px #000000;
} }
.ks-designer-node { .ks-designer-node {
background: #1b3875; background: #1b3875;
//background: url('@/assets/icons/bg-node.png') center / 100% 100%; //background: url('@/assets/icons/bg-node.png') center / 100% 100%;
@@ -134,7 +133,6 @@ export default defineComponent({
&:hover { &:hover {
box-shadow: 0 1px 2px -2px rgb(0 0 0), 0 3px 6px 0 rgb(0 0 0 / 60%), 0 5px 12px 4px rgb(0 0 0 / 30%); box-shadow: 0 1px 2px -2px rgb(0 0 0), 0 3px 6px 0 rgb(0 0 0 / 60%), 0 5px 12px 4px rgb(0 0 0 / 30%);
} }
.ant-card-head { .ant-card-head {
border: 0; border: 0;
height: 30px; height: 30px;
@@ -166,25 +164,20 @@ export default defineComponent({
//} //}
&.ks-designer-node-select { &.ks-designer-node-select {
background: #255464; background: #255464;
.ant-card-body{ .ant-card-body{
background: #1c4654; background: #1c4654;
} }
} }
&.ks-designer-node-precondition, &.ks-designer-node-precondition,
&.ks-designer-node-parallel, &.ks-designer-node-parallel,
&.ks-designer-node-sequence{ &.ks-designer-node-sequence{
background: #4c5a9d; background: #4c5a9d;
.ant-card-body{ .ant-card-body{
background: #3f4d8d; background: #3f4d8d;
} }
} }
&.ks-designer-node-action{ &.ks-designer-node-action{
background: #645525; background: #645525;
.ant-card-body{ .ant-card-body{
background: #726334; background: #726334;
} }

View File

@@ -7,7 +7,7 @@
* that was distributed with this source code. * that was distributed with this source code.
*/ */
import type { NodeTemplateData, NodeTemplateQuery, TreeModelsData } from './types'; import type { TreeModelsData, NodeTemplateQuery, NodeTemplateData } from './types';
import type { ApiPagination, ApiPaginationQuery } from '@/types'; import type { ApiPagination, ApiPaginationQuery } from '@/types';
export const defaultPagination = { export const defaultPagination = {
@@ -26,16 +26,16 @@ export const defaultPaginationRequest = {
page: 1, page: 1,
limit: 10, limit: 10,
keyword: null, keyword: null,
} as ApiPaginationQuery; } as ApiPaginationQuery
export const defaultNodeTemplateQuery = { export const defaultNodeTemplateQuery = {
page: 1, page: 1,
limit: 1000, limit: 1000,
keyword: null, keyword: null,
include_params: true, include_params: true,
} as NodeTemplateQuery; } as NodeTemplateQuery
export const defaultNodeTemplateData = { export const defaultNodeTemplateData = {
templates: [], templates: [],
total: 0, total: 0
} as NodeTemplateData; } as NodeTemplateData

View File

@@ -46,10 +46,10 @@
</div> </div>
<Properties <Properties
v-if="graph" v-if="graph"
@update-element="handleUpdateElement"
:element="selectedNodeTaskElement" :element="selectedNodeTaskElement"
:graph="graph as any" :graph="graph as any"
:node="selectedModelNode as any" :node="selectedModelNode as any" />
@update-element="handleUpdateElement" />
</div> </div>
</a-layout> </a-layout>
</a-layout> </a-layout>
@@ -60,7 +60,7 @@
import { defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'; import { defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { getTeleport } from '@antv/x6-vue-shape'; import { getTeleport } from '@antv/x6-vue-shape';
import { Graph, Node, type NodeProperties } from '@antv/x6'; import { Graph, Node,Edge, type NodeProperties } from '@antv/x6';
import { CheckCircleOutlined, CheckOutlined, RollbackOutlined, SaveOutlined } from '@ant-design/icons-vue'; import { CheckCircleOutlined, CheckOutlined, RollbackOutlined, SaveOutlined } from '@ant-design/icons-vue';
import { Wrapper } from '@/components/wrapper'; import { Wrapper } from '@/components/wrapper';
import { safePreventDefault, safeStopPropagation } from '@/utils/event'; import { safePreventDefault, safeStopPropagation } from '@/utils/event';
@@ -100,7 +100,7 @@ export default defineComponent({
const currentTreeModelGraph = ref<TreeModelGraph | null>(null); const currentTreeModelGraph = ref<TreeModelGraph | null>(null);
const selectedModelNode = ref<Node<NodeProperties> | null>(null); const selectedModelNode = ref<Node<NodeProperties> | null>(null);
const selectedNodeTaskElement = ref<SettingTaskNodeElement | null>(null); const selectedNodeTaskElement = ref<SettingTaskNodeElement | null>(null);
const changed = ref<boolean>(false); const changed = ref<boolean>(false)
const { const {
handleGraphEvent, handleGraphEvent,
@@ -210,7 +210,7 @@ export default defineComponent({
currentTreeModelGraph.value.graph = { currentTreeModelGraph.value.graph = {
nodes: [], nodes: [],
edges: [], edges: [],
}; }
} }
const nodes = currentTreeModelGraph.value?.graph?.nodes ?? []; const nodes = currentTreeModelGraph.value?.graph?.nodes ?? [];
const edges = currentTreeModelGraph.value?.graph?.edges ?? []; const edges = currentTreeModelGraph.value?.graph?.edges ?? [];
@@ -218,15 +218,15 @@ export default defineComponent({
nodes.forEach(n=> { nodes.forEach(n=> {
const node = createTaskNodeElement(n); const node = createTaskNodeElement(n);
graph.value?.addNode(node as Node); graph.value?.addNode(node as Node);
}); })
edges.forEach(g=> { edges.forEach(g=> {
graph.value?.addEdge( g as any); graph.value?.addEdge( g as any);
}); })
} catch (e){ } catch (e){
console.warn('createElements', e); console.warn('createElements',e)
} }
}); })
}, 200); }, 200)
} }
}; };
@@ -290,7 +290,7 @@ export default defineComponent({
console.info('handleUpdateElement', element); console.info('handleUpdateElement', element);
// 更新本地引用 // 更新本地引用
selectedNodeTaskElement.value = element; selectedNodeTaskElement.value = element;
changed.value = true; changed.value = true
}; };
const handleSelectTree = (treeModel: TreeModel) => { const handleSelectTree = (treeModel: TreeModel) => {

View File

@@ -7,7 +7,7 @@
</template> </template>
<div class="w-full h-full"> <div class="w-full h-full">
<a-row> <a-row>
<a-col v-for="nm in controlTemplates" :span="12"> <a-col :span="12" v-for="nm in controlTemplates">
<div <div
:key="nm.id" :key="nm.id"
:data-type="nm.type" :data-type="nm.type"
@@ -15,7 +15,7 @@
@dragend="handleDragEnd" @dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)" @dragstart="handleDragStart($event, nm)"
> >
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" /> <img class="icon" src="@/assets/icons/model-4.svg" :alt="nm.name ?? ''" />
<span class="desc">{{ nm.name }}</span> <span class="desc">{{ nm.name }}</span>
</div> </div>
</a-col> </a-col>
@@ -28,7 +28,7 @@
</template> </template>
<div class="w-full h-full"> <div class="w-full h-full">
<a-row> <a-row>
<a-col v-for="nm in conditionTemplates" :span="12"> <a-col :span="12" v-for="nm in conditionTemplates">
<div <div
:key="nm.id" :key="nm.id"
:data-type="nm.type" :data-type="nm.type"
@@ -36,7 +36,7 @@
@dragend="handleDragEnd" @dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)" @dragstart="handleDragStart($event, nm)"
> >
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" /> <img class="icon" src="@/assets/icons/model-4.svg" :alt="nm.name ?? ''" />
<span class="desc">{{ nm.name }}</span> <span class="desc">{{ nm.name }}</span>
</div> </div>
</a-col> </a-col>
@@ -49,7 +49,7 @@
</template> </template>
<div class="w-full h-full"> <div class="w-full h-full">
<a-row> <a-row>
<a-col v-for="nm in actionsTemplates" :span="12"> <a-col :span="12" v-for="nm in actionsTemplates">
<div <div
:key="nm.id" :key="nm.id"
:data-type="nm.type" :data-type="nm.type"
@@ -57,7 +57,7 @@
@dragend="handleDragEnd" @dragend="handleDragEnd"
@dragstart="handleDragStart($event, nm)" @dragstart="handleDragStart($event, nm)"
> >
<img :alt="nm.name ?? ''" class="icon" src="@/assets/icons/model-4.svg" /> <img class="icon" src="@/assets/icons/model-4.svg" :alt="nm.name ?? ''" />
<span class="desc">{{ nm.name }}</span> <span class="desc">{{ nm.name }}</span>
</div> </div>
</a-col> </a-col>
@@ -212,6 +212,6 @@ export default defineComponent({
handleDragEnd, handleDragEnd,
}; };
}, },
}); })
</script> </script>

View File

@@ -14,28 +14,28 @@
style="padding-bottom:15px;" style="padding-bottom:15px;"
> >
<a-form-item label="节点名称"> <a-form-item label="节点名称">
<a-input v-model:value="currentElement.name" :placeholder="currentElement.name" size="small" /> <a-input size="small" v-model:value="currentElement.name" :placeholder="currentElement.name" />
</a-form-item> </a-form-item>
<a-form-item label="节点介绍"> <a-form-item label="节点介绍">
<a-textarea v-model:value="currentElement.description" :placeholder="currentElement.description" size="small" /> <a-textarea size="small" v-model:value="currentElement.description" :placeholder="currentElement.description" />
</a-form-item> </a-form-item>
<a-divider /> <a-divider />
<a-form-item label="输入"> <a-form-item label="输入">
<a-textarea v-model:value="currentElement.inputs" size="small" /> <a-textarea size="small" v-model:value="currentElement.inputs" />
</a-form-item> </a-form-item>
<a-form-item label="输出"> <a-form-item label="输出">
<a-textarea v-model:value="currentElement.outputs" size="small" /> <a-textarea size="small" v-model:value="currentElement.outputs" />
</a-form-item> </a-form-item>
<a-divider v-if="currentElement.settings && currentElement.settings.length > 0"/> <a-divider v-if="currentElement.settings && currentElement.settings.length > 0"/>
<a-form-item v-for="setting in currentElement.settings" :label="setting.description"> <a-form-item :label="setting.description" v-for="setting in currentElement.settings">
<a-input-number v-if="setting.data_type === 'double'" v-model:value="setting.default_value" :placeholder="setting.description" size="small" style="width:100%;" /> <a-input-number size="small" style="width:100%;" v-if="setting.data_type === 'double'" v-model:value="setting.default_value" :placeholder="setting.description" />
<a-input v-else v-model:value="setting.default_value" :placeholder="setting.description" size="small" /> <a-input v-else size="small" v-model:value="setting.default_value" :placeholder="setting.description" />
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-tab-pane> </a-tab-pane>
@@ -113,7 +113,7 @@ import { defineComponent, onMounted, type PropType, ref, watch } from 'vue';
import { CheckOutlined } from '@ant-design/icons-vue'; import { CheckOutlined } from '@ant-design/icons-vue';
import type { ElementVariable, SettingTaskNodeElement } from './types'; import type { ElementVariable, SettingTaskNodeElement } from './types';
import type { Graph, Node, NodeProperties } from '@antv/x6'; import type { Graph, Node, NodeProperties } from '@antv/x6';
import { generateKey } from '@/utils/strings'; import {generateKey} from '@/utils/strings'
const actionSpaceColumns = [ const actionSpaceColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', width: 40 }, { title: '序号', dataIndex: 'index', key: 'index', width: 40 },
@@ -127,7 +127,7 @@ export default defineComponent({
components: { CheckOutlined }, components: { CheckOutlined },
props: { props: {
node: { type: [Object, null] as PropType<Node<NodeProperties> | null | undefined>, required: false }, node: { type: [Object, null] as PropType<Node<NodeProperties> | null | undefined>, required: false },
graph: { type: [Object, null] as PropType<Graph | null | undefined>, required: true }, graph: { type: [Object, null] as PropType<Graph | null | undefined>, required: true }
}, },
emits: ['update-element'], emits: ['update-element'],
setup(props, { emit }) { setup(props, { emit }) {
@@ -165,8 +165,8 @@ export default defineComponent({
value: null, value: null,
defaults: null, defaults: null,
unit: null, unit: null,
}); })
}; }
const removeVariable = (row: ElementVariable) => { const removeVariable = (row: ElementVariable) => {
if (currentElement.value && currentElement.value.variables) { if (currentElement.value && currentElement.value.variables) {

View File

@@ -1,10 +1,11 @@
<template> <template>
<a-collapse v-model:activeKey="activeKey" :accordion="false"> <a-collapse v-model:activeKey="activeKey" :accordion="false">
<a-collapse-panel key="1"> <a-collapse-panel key="1">
<template #header> <template #header>
<span class="ks-model-builder-title-icon icon-model"></span>我的行为树 <span class="ks-model-builder-title-icon icon-model"></span>我的行为树
</template> </template>
<a-list :data-source="treeModelsData.trees || []" size="small" style="min-height: 25vh"> <a-list size="small" :data-source="treeModelsData.trees || []" style="min-height: 25vh">
<template #renderItem="{ item }"> <template #renderItem="{ item }">
<a-tooltip placement="right"> <a-tooltip placement="right">
<template #title> <template #title>
@@ -15,15 +16,58 @@
</a-list-item> </a-list-item>
</a-tooltip> </a-tooltip>
</template> </template>
<!-- <template #footer>-->
<!-- <div>Footer</div>-->
<!-- </template>-->
</a-list> </a-list>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </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> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, onMounted, ref } from 'vue'; import { defineComponent, onMounted, ref } from 'vue';
import { defaultPaginationRequest, defaultTreeModelsData } from './constants'; import { defaultPaginationRequest, defaultTreeModelsData } from './constants';
import { PlusOutlined } from '@ant-design/icons-vue'; import { CheckOutlined, PlusOutlined } from '@ant-design/icons-vue';
import type { ApiPaginationQuery } from '@/types'; import type { ApiPaginationQuery } from '@/types';
import type { TreeModel, TreeModelsData } from './types'; import type { TreeModel, TreeModelsData } from './types';
import { findTreesByQuery } from './api'; import { findTreesByQuery } from './api';
@@ -31,12 +75,12 @@ import { findTreesByQuery } from './api';
export default defineComponent({ export default defineComponent({
emits: ['select-tree'], emits: ['select-tree'],
components: { components: {
PlusOutlined, PlusOutlined
}, },
setup(_props, { emit }) { setup(_props, { emit }) {
const treeModelsData = ref<TreeModelsData>({ ...defaultTreeModelsData }); const treeModelsData = ref<TreeModelsData>({ ...defaultTreeModelsData });
const treeModelsQuery = ref<ApiPaginationQuery>({ ...defaultPaginationRequest }); const treeModelsQuery = ref<ApiPaginationQuery>({ ...defaultPaginationRequest });
const activeKey = ref<number>(1); const activeKey = ref<number>(1)
const loadTress = () => { const loadTress = () => {
findTreesByQuery(treeModelsQuery.value).then(r => { findTreesByQuery(treeModelsQuery.value).then(r => {
treeModelsData.value = r.data; treeModelsData.value = r.data;
@@ -52,7 +96,7 @@ export default defineComponent({
const handleSelect = (record: TreeModel) => { const handleSelect = (record: TreeModel) => {
emit('select-tree', record); emit('select-tree', record);
}; }
const customRow = (record: TreeModel) => { const customRow = (record: TreeModel) => {
return { return {
@@ -76,7 +120,7 @@ export default defineComponent({
handleSelect, handleSelect,
}; };
}, },
}); })
</script> </script>
@@ -85,7 +129,6 @@ export default defineComponent({
.create-tree-icon{ .create-tree-icon{
cursor: pointer; cursor: pointer;
} }
.ant-list-item { .ant-list-item {
padding: 5px 5px; padding: 5px 5px;
cursor: pointer; cursor: pointer;

View File

@@ -7,7 +7,7 @@
* that was distributed with this source code. * that was distributed with this source code.
*/ */
export * from './tree'; export * from './tree'
export * from './template'; export * from './template'
export * from './parameter'; export * from './parameter'
export * from './node'; export * from './node'

View File

@@ -7,7 +7,7 @@
* that was distributed with this source code. * that was distributed with this source code.
*/ */
import type { ApiPaginationQuery, ApiResponse, NullableString } from '@/types'; import type { ApiResponse, NullableString ,ApiPaginationQuery} from '@/types';
import type { NodeSetting } from './parameter'; import type { NodeSetting } from './parameter';
export interface NodeTemplate { export interface NodeTemplate {

View File

@@ -15,8 +15,8 @@ export const createTaskNodeElementFromTemplate = (
template: NodeTemplate, template: NodeTemplate,
rect?: TaskNodeRect, rect?: TaskNodeRect,
): SettingTaskNodeElement => { ): SettingTaskNodeElement => {
let realRect = { width: 200, height: 100, x: 0, y: 0, ...rect || {} }; let realRect = { width: 200, height: 100, x: 0, y: 0, ...rect || {} }
console.info('rect', rect); console.info('rect',rect)
return { return {
id: 0, id: 0,
key: generateKey(template.type), key: generateKey(template.type),
@@ -41,15 +41,15 @@ export const createTaskNodeElementFromTemplate = (
name: '范围', name: '范围',
value: '1000', value: '1000',
defaults: '1000', defaults: '1000',
unit: 'KM', unit: 'KM'
}, },
{ {
key: generateKey('var_'), key: generateKey('var_'),
name: '武器名称', name: '武器名称',
value: '地对空导弹', value: '地对空导弹',
defaults: '地对空导弹', defaults: '地对空导弹',
unit: '个', unit: '个'
}, }
], ],
} as SettingTaskNodeElement; } as SettingTaskNodeElement;
}; };
@@ -135,7 +135,7 @@ export const hasElements = (graph: Graph): boolean => {
return taskElements.length > 0; return taskElements.length > 0;
} }
return false; return false;
}; }
export const hasRootElementNode = (graph: Graph): boolean => { export const hasRootElementNode = (graph: Graph): boolean => {
if (graph) { if (graph) {