2026-02-08 15:59:14 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="ks-model-builder-right">
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<template v-if="currentElement || tree">
|
2026-02-08 15:59:14 +08:00
|
|
|
|
|
|
|
|
|
|
<a-tabs v-model:activeKey="activeTopTabsKey" class="ks-model-builder-tabs settings-tab">
|
|
|
|
|
|
<template #leftExtra>
|
|
|
|
|
|
<span class="ks-model-builder-title-icon icon-input"></span>
|
|
|
|
|
|
</template>
|
2026-02-08 22:09:28 +08:00
|
|
|
|
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<a-tab-pane v-if="tree" key="1" tab="行为树属性">
|
2026-02-08 22:09:28 +08:00
|
|
|
|
<a-form
|
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
name="basic"
|
|
|
|
|
|
style="padding-bottom:15px;"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-form-item label="行为树名称">
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<a-input v-model:value="tree.name" placeholder="行为树名称" size="small" />
|
2026-02-08 22:09:28 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
2026-02-08 22:28:52 +08:00
|
|
|
|
<a-form-item label="行为树英文名称">
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<a-input v-model:value="tree.englishName" placeholder="行为树英文名称" size="small" />
|
2026-02-08 22:28:52 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
2026-02-08 22:09:28 +08:00
|
|
|
|
<a-form-item label="行为树说明">
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<a-textarea v-model:value="tree.description" placeholder="行为树说明" size="small" />
|
2026-02-08 22:09:28 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
</a-form>
|
|
|
|
|
|
</a-tab-pane>
|
|
|
|
|
|
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<a-tab-pane v-if="currentElement" key="2" tab="节点属性">
|
2026-02-08 15:59:14 +08:00
|
|
|
|
<a-form
|
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
name="basic"
|
|
|
|
|
|
style="padding-bottom:15px;"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-form-item label="节点名称">
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<a-input v-model:value="currentElement.name" :placeholder="currentElement.name" size="small" />
|
2026-02-08 15:59:14 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item label="节点介绍">
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<a-textarea v-model:value="currentElement.description" :placeholder="currentElement.description" size="small" />
|
2026-02-08 15:59:14 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
2026-03-13 10:40:44 +08:00
|
|
|
|
<a-form-item label="排序">
|
|
|
|
|
|
<a-input-number style="width:100%;" v-model:value="currentElement.order" size="small" />
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
2026-02-08 15:59:14 +08:00
|
|
|
|
<a-divider />
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item label="输入">
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<a-textarea v-model:value="currentElement.inputs" size="small" />
|
2026-02-08 15:59:14 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item label="输出">
|
2026-02-08 22:31:13 +08:00
|
|
|
|
<a-textarea v-model:value="currentElement.outputs" size="small" />
|
2026-02-08 15:59:14 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
2026-03-13 10:40:44 +08:00
|
|
|
|
<!-- <a-divider v-if="currentElement.settings && currentElement.parameters.length > 0" />-->
|
2026-02-08 15:59:14 +08:00
|
|
|
|
|
2026-03-13 10:40:44 +08:00
|
|
|
|
<!-- <a-form-item v-for="setting in currentElement.parameters" :label="setting.description">-->
|
|
|
|
|
|
<!-- <a-input-number v-if="setting.dataType === 'double'" v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" style="width:100%;" />-->
|
|
|
|
|
|
<!-- <a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />-->
|
|
|
|
|
|
<!-- </a-form-item>-->
|
2026-02-08 15:59:14 +08:00
|
|
|
|
</a-form>
|
|
|
|
|
|
</a-tab-pane>
|
|
|
|
|
|
|
|
|
|
|
|
</a-tabs>
|
|
|
|
|
|
|
2026-03-27 09:33:46 +08:00
|
|
|
|
<a-tabs v-if="currentElement?.parameters && currentElement?.parameters.length > 0" v-model:activeKey="activeBottomTabsKey" class="ks-model-builder-tabs parameters-tabs">
|
2026-02-08 15:59:14 +08:00
|
|
|
|
<template #leftExtra>
|
|
|
|
|
|
<span class="ks-model-builder-title-icon icon-input"></span>
|
|
|
|
|
|
</template>
|
2026-03-26 22:05:31 +08:00
|
|
|
|
|
2026-03-26 23:36:47 +08:00
|
|
|
|
<template #rightExtra v-if="multiableParameters">
|
|
|
|
|
|
<a-tooltip title="添加平台" placement="left">
|
|
|
|
|
|
<div class="ks-add-parameter-action" @click="()=> addParameterTab()">
|
|
|
|
|
|
<a-space>
|
|
|
|
|
|
<PlusCircleOutlined/>
|
|
|
|
|
|
<span>添加</span>
|
|
|
|
|
|
</a-space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<a-tab-pane key="1" tab="节点变量">
|
|
|
|
|
|
<template v-if="currentElement.parameters && currentElement.parameters.length > 0">
|
|
|
|
|
|
<a-form
|
|
|
|
|
|
autocomplete="off"
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
name="basic"
|
|
|
|
|
|
style="padding-bottom:15px;"
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
|
|
|
<template v-if="multiableParameters">
|
|
|
|
|
|
<a-tabs class="ks-parameter-setting-tabs"
|
|
|
|
|
|
v-model:activeKey="groupedParametersActiveTab"
|
|
|
|
|
|
tab-position="left"
|
|
|
|
|
|
hide-add
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
type="editable-card"
|
|
|
|
|
|
@edit="onEditParameterTab">
|
2026-04-13 17:26:50 +08:00
|
|
|
|
<a-tab-pane v-for="(grouped,index) in groupedParameters" :key="index" :tab="getPlatformTabName(index)" :closable="true">
|
2026-03-26 23:36:47 +08:00
|
|
|
|
<a-form-item v-for="setting in grouped" :label="setting.description">
|
2026-03-27 00:05:04 +08:00
|
|
|
|
<a-input-number v-if="setting.dataType === 'double'"
|
|
|
|
|
|
v-model:value="setting.defaultValue"
|
2026-03-26 23:36:47 +08:00
|
|
|
|
:placeholder="setting.description" size="small" style="width:100%;" />
|
2026-03-27 00:05:04 +08:00
|
|
|
|
<a-select :placeholder="`请选择${setting.description}`"
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
v-else-if="setting.paramKey === 'platforms'" v-model:value="setting.defaultValue">
|
2026-04-13 17:26:50 +08:00
|
|
|
|
<a-select-option v-for="pl in getAvailablePlatforms()" :value="pl.name">{{pl.description}}</a-select-option>
|
2026-03-27 00:05:04 +08:00
|
|
|
|
</a-select>
|
2026-03-31 14:32:54 +08:00
|
|
|
|
<a-select :placeholder="`请选择${setting.description}`"
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
v-else-if="setting.paramKey === 'command'" v-model:value="setting.defaultValue">
|
|
|
|
|
|
<a-select-option v-for="pl in nodeCommands" :value="pl.command">{{pl.chineseName}}</a-select-option>
|
|
|
|
|
|
</a-select>
|
2026-03-27 00:05:04 +08:00
|
|
|
|
<a-input v-else v-model:value="setting.defaultValue"
|
|
|
|
|
|
:placeholder="setting.description" size="small" />
|
2026-03-26 23:36:47 +08:00
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</a-tab-pane>
|
|
|
|
|
|
<!-- <template #leftExtra>-->
|
|
|
|
|
|
<!-- <a-button class="ks-btn" size="small" style="margin-bottom: 15px; width: 100%" block @click="()=> addParameterTab()">-->
|
|
|
|
|
|
<!-- <PlusOutlined />-->
|
|
|
|
|
|
<!--<!– <span>添加</span>–>-->
|
|
|
|
|
|
<!-- </a-button>-->
|
|
|
|
|
|
<!-- </template>-->
|
|
|
|
|
|
</a-tabs>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
2026-04-15 17:56:02 +08:00
|
|
|
|
<template v-for="setting in currentElement.parameters">
|
|
|
|
|
|
<div v-if="['lon','lat'].includes(setting.paramKey as string)">
|
|
|
|
|
|
<div v-if="setting.paramKey==='lon'" class="ks-location-title">位置</div>
|
|
|
|
|
|
<a-form-item class="ks-location-item" labelAlign="left" :label="setting.description">
|
|
|
|
|
|
<a-input-number v-if="setting.dataType === 'double'" v-model:value="setting.defaultValue"
|
|
|
|
|
|
:placeholder="setting.description" size="small" style="width:100%;" />
|
|
|
|
|
|
<a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-form-item v-else :label="setting.description">
|
|
|
|
|
|
<a-input-number v-if="setting.dataType === 'double'" v-model:value="setting.defaultValue"
|
|
|
|
|
|
:placeholder="setting.description" size="small" style="width:100%;" />
|
|
|
|
|
|
<a-select :placeholder="`请选择${setting.description}`"
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
v-else-if="setting.paramKey === 'platforms'" v-model:value="setting.defaultValue">
|
|
|
|
|
|
<a-select-option v-for="pl in getAvailablePlatforms()" :value="pl.name">{{ pl.description }}</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
<a-select :placeholder="`请选择${setting.description}`"
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
v-else-if="setting.paramKey === 'command'" v-model:value="setting.defaultValue">
|
|
|
|
|
|
<a-select-option v-for="pl in nodeCommands" :value="pl.command">{{pl.chineseName}}</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
<a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</template>
|
2026-03-26 23:36:47 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</a-form>
|
|
|
|
|
|
</template>
|
2026-03-13 10:40:44 +08:00
|
|
|
|
<a-empty v-else>
|
|
|
|
|
|
</a-empty>
|
2026-02-08 15:59:14 +08:00
|
|
|
|
</a-tab-pane>
|
|
|
|
|
|
</a-tabs>
|
|
|
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<a-tabs v-else :activeKey="'0'" class="ks-model-builder-tabs parameters-tabs empty">
|
|
|
|
|
|
<template #leftExtra>
|
|
|
|
|
|
<span class="ks-model-builder-title-icon icon-input"></span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<a-tab-pane :key="'0'" tab="请选择或者创建决策树">
|
|
|
|
|
|
<a-empty>
|
|
|
|
|
|
<template #description>
|
|
|
|
|
|
请选择或者创建决策树
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-empty>
|
|
|
|
|
|
</a-tab-pane>
|
|
|
|
|
|
</a-tabs>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
|
import { defineComponent, onMounted, type PropType, ref, watch } from 'vue';
|
2026-03-26 23:36:47 +08:00
|
|
|
|
import { CheckOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
|
|
|
|
|
import type { ElementParameter, ElementVariable, GraphTaskElement } from '../graph';
|
2026-03-15 16:36:07 +08:00
|
|
|
|
import type { BehaviorTree } from './tree';
|
2026-02-08 15:59:14 +08:00
|
|
|
|
import type { Graph, Node, NodeProperties } from '@antv/x6';
|
2026-02-08 20:27:40 +08:00
|
|
|
|
import { generateKey } from '@/utils/strings';
|
2026-03-31 14:32:54 +08:00
|
|
|
|
import type { NodeCommand, Platform } from '../types';
|
2026-02-08 15:59:14 +08:00
|
|
|
|
|
|
|
|
|
|
const actionSpaceColumns = [
|
|
|
|
|
|
{ title: '序号', dataIndex: 'index', key: 'index', width: 40 },
|
|
|
|
|
|
{ title: '变量名', dataIndex: 'name', key: 'name', width: 80 },
|
|
|
|
|
|
{ title: '参数值', dataIndex: 'value', key: 'value', width: 80 },
|
|
|
|
|
|
{ title: '单位', dataIndex: 'unit', key: 'unit', width: 80 },
|
|
|
|
|
|
{ title: '操作', dataIndex: '_actions', key: '_actions', width: 60 },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
export default defineComponent({
|
2026-03-26 23:36:47 +08:00
|
|
|
|
components: { CheckOutlined, PlusOutlined, PlusCircleOutlined },
|
2026-02-08 15:59:14 +08:00
|
|
|
|
props: {
|
2026-02-08 22:09:28 +08:00
|
|
|
|
tree: { type: [Object, null] as PropType<BehaviorTree | null | undefined>, required: false },
|
2026-02-08 22:31:13 +08:00
|
|
|
|
treeEditing: { type: Boolean as PropType<boolean>, required: true, default: false },
|
2026-02-08 15:59:14 +08:00
|
|
|
|
node: { type: [Object, null] as PropType<Node<NodeProperties> | null | undefined>, required: false },
|
2026-02-08 22:31:13 +08:00
|
|
|
|
graph: { type: [Object, null] as PropType<Graph | null | undefined>, required: true },
|
2026-03-27 00:05:04 +08:00
|
|
|
|
platforms: { type: Array as PropType<Platform[]>, required: true },
|
2026-04-13 17:26:50 +08:00
|
|
|
|
subPlatforms: { type: Array as PropType<Platform[]>, required: false, default: () => [] },
|
2026-03-31 14:32:54 +08:00
|
|
|
|
nodeCommands: { type: Array as PropType<NodeCommand[]>, required: true },
|
2026-02-08 15:59:14 +08:00
|
|
|
|
},
|
2026-02-08 22:09:28 +08:00
|
|
|
|
emits: ['update-element', 'update-tree'],
|
2026-02-08 15:59:14 +08:00
|
|
|
|
setup(props, { emit }) {
|
2026-03-27 00:05:04 +08:00
|
|
|
|
const platforms = ref<Platform[]>(props.platforms ?? []);
|
2026-04-13 17:26:50 +08:00
|
|
|
|
const subPlatforms = ref<Platform[]>(props.subPlatforms ?? []);
|
2026-03-31 14:32:54 +08:00
|
|
|
|
const nodeCommands = ref<NodeCommand[]>(props.nodeCommands ?? []);
|
2026-03-27 00:05:04 +08:00
|
|
|
|
|
2026-02-08 15:59:14 +08:00
|
|
|
|
const activeTopTabsKey = ref<string>('1');
|
|
|
|
|
|
const activeBottomTabsKey = ref<string>('1');
|
|
|
|
|
|
const activeBottomTabs2Key = ref<string>('1');
|
|
|
|
|
|
|
2026-02-08 22:31:13 +08:00
|
|
|
|
const currentTree = ref<BehaviorTree | null>(props.tree ?? null);
|
2026-02-08 22:09:28 +08:00
|
|
|
|
const treeEditing = ref<boolean>(props.treeEditing);
|
2026-02-08 15:59:14 +08:00
|
|
|
|
const currentNode = ref<Node | null>(props.node ?? null);
|
2026-02-08 20:27:40 +08:00
|
|
|
|
const currentElement = ref<GraphTaskElement | null>(null);
|
2026-02-08 15:59:14 +08:00
|
|
|
|
|
2026-03-26 23:36:47 +08:00
|
|
|
|
const emptyParameters = ref<ElementParameter[]>([]);
|
|
|
|
|
|
const groupedParameters = ref<Array<ElementParameter[]>>([]);
|
|
|
|
|
|
const multiableParameters = ref<boolean>(false);
|
|
|
|
|
|
const groupedParametersActiveTab = ref<number>(0);
|
|
|
|
|
|
|
|
|
|
|
|
const createEmptyParameters = (): ElementParameter[] => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return JSON.parse(JSON.stringify(currentElement.value?.parameters ?? [])) as ElementParameter[];
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const dumpParameters = (): ElementParameter[] => {
|
|
|
|
|
|
return JSON.parse(JSON.stringify(emptyParameters.value)) as ElementParameter[];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const addParameterTab = () => {
|
|
|
|
|
|
let newParameters = dumpParameters();
|
2026-04-15 17:22:22 +08:00
|
|
|
|
// 如果有下属平台,预填对应索引的平台名称
|
|
|
|
|
|
const nextIndex = groupedParameters.value.length;
|
|
|
|
|
|
const subPlatform = subPlatforms.value[nextIndex];
|
|
|
|
|
|
if (subPlatform) {
|
|
|
|
|
|
const platformParam = newParameters.find(p => p.paramKey === 'platforms');
|
|
|
|
|
|
if (platformParam) {
|
|
|
|
|
|
platformParam.defaultValue = subPlatform.name;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 新增一个参数分组
|
2026-03-26 23:36:47 +08:00
|
|
|
|
groupedParameters.value.push(newParameters);
|
|
|
|
|
|
// 自动切换到新增的分组
|
|
|
|
|
|
groupedParametersActiveTab.value = groupedParameters.value.length - 1;
|
2026-02-08 15:59:14 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-26 23:36:47 +08:00
|
|
|
|
|
|
|
|
|
|
const removeParameterTab = (index: number) => {
|
|
|
|
|
|
// 边界判断:防止删除不存在的分组 / 只剩一个分组时禁止删除
|
|
|
|
|
|
if (index < 0 || index >= groupedParameters.value.length) return;
|
|
|
|
|
|
if (groupedParameters.value.length <= 1) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 从数组中删除对应索引的分组
|
|
|
|
|
|
groupedParameters.value.splice(index, 1);
|
|
|
|
|
|
|
|
|
|
|
|
if (groupedParameters.value.length === 0) {
|
|
|
|
|
|
groupedParameters.value.push(dumpParameters());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除后处理激活状态:
|
|
|
|
|
|
// 如果删除的是最后一个分组,激活前一个
|
|
|
|
|
|
if (groupedParametersActiveTab.value >= groupedParameters.value.length) {
|
|
|
|
|
|
groupedParametersActiveTab.value = groupedParameters.value.length - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onEditParameterTab = (targetKey: number | MouseEvent, action: string) => {
|
|
|
|
|
|
if (action === 'add') {
|
|
|
|
|
|
addParameterTab();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
removeParameterTab(targetKey as number);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const resolveGroupedParameters = () => {
|
|
|
|
|
|
emptyParameters.value = createEmptyParameters();
|
|
|
|
|
|
// 解构获取当前元素的关键属性,简化代码
|
|
|
|
|
|
const { multiable, parameters } = currentElement.value || {};
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 不满足多分组条件:直接清空分组
|
|
|
|
|
|
if (multiable !== true || !parameters || parameters.length === 0) {
|
|
|
|
|
|
groupedParameters.value = [];
|
|
|
|
|
|
multiableParameters.value = multiable === true;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 满足条件:根据 groupIndex 对参数进行分组
|
|
|
|
|
|
// 第一步:用 Map 做临时分组(key=groupIndex,value=当前分组的参数数组)
|
|
|
|
|
|
const groupMap = new Map<number, ElementParameter[]>();
|
|
|
|
|
|
parameters.forEach(param => {
|
|
|
|
|
|
const index = param.groupIndex;
|
|
|
|
|
|
// 如果 Map 中没有该分组,先初始化空数组
|
|
|
|
|
|
if (!groupMap.has(index)) {
|
|
|
|
|
|
groupMap.set(index, []);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 将当前参数推入对应分组
|
|
|
|
|
|
groupMap.get(index)!.push(param);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 第二步:将 Map 转换为二维数组(按 groupIndex 升序排序)
|
|
|
|
|
|
groupedParameters.value = Array.from(groupMap.entries())
|
|
|
|
|
|
// 按分组索引从小到大排序(保证分组顺序正确)
|
|
|
|
|
|
.sort((a, b) => a[0] - b[0])
|
|
|
|
|
|
// 只保留分组后的参数数组,丢弃 key
|
|
|
|
|
|
.map(item => item[1]);
|
|
|
|
|
|
|
|
|
|
|
|
multiableParameters.value = multiable === true && groupedParameters.value.length > 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-15 17:22:22 +08:00
|
|
|
|
// 获取平台Tab显示名称(优先使用下属平台名称)
|
2026-04-13 17:26:50 +08:00
|
|
|
|
const getPlatformTabName = (index: number): string => {
|
2026-04-15 17:22:22 +08:00
|
|
|
|
const sub = subPlatforms.value[index];
|
|
|
|
|
|
if (sub) {
|
|
|
|
|
|
return sub.name || sub.description || `平台 ${index + 1}`;
|
2026-04-13 17:26:50 +08:00
|
|
|
|
}
|
2026-04-15 17:22:22 +08:00
|
|
|
|
return `平台 ${index + 1}`;
|
2026-04-13 17:26:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取可用的平台列表(包括当前平台和其下属平台)
|
|
|
|
|
|
const getAvailablePlatforms = (): Platform[] => {
|
|
|
|
|
|
if (!currentTree.value?.platformId) {
|
|
|
|
|
|
return platforms.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有下属平台,返回下属平台列表
|
|
|
|
|
|
if (subPlatforms.value && subPlatforms.value.length > 0) {
|
|
|
|
|
|
return subPlatforms.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 否则返回所有平台
|
|
|
|
|
|
return platforms.value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-26 23:36:47 +08:00
|
|
|
|
|
2026-02-08 15:59:14 +08:00
|
|
|
|
const resolveNode = (n?: Node | null | undefined) => {
|
2026-03-26 23:36:47 +08:00
|
|
|
|
groupedParametersActiveTab.value = 0;
|
2026-02-08 15:59:14 +08:00
|
|
|
|
currentNode.value = n ?? null;
|
|
|
|
|
|
if (n) {
|
|
|
|
|
|
const data = n.getData();
|
2026-02-08 20:27:40 +08:00
|
|
|
|
currentElement.value = JSON.parse(JSON.stringify(data || {})) as GraphTaskElement;
|
2026-02-08 15:59:14 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
currentElement.value = null;
|
|
|
|
|
|
}
|
2026-03-26 23:36:47 +08:00
|
|
|
|
resolveGroupedParameters();
|
2026-02-08 15:59:14 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-08 22:31:13 +08:00
|
|
|
|
const addVariable = () => {
|
2026-02-08 15:59:14 +08:00
|
|
|
|
if (!currentElement.value) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!currentElement.value.variables) {
|
|
|
|
|
|
currentElement.value.variables = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
currentElement.value?.variables.push({
|
|
|
|
|
|
key: generateKey('variable'),
|
|
|
|
|
|
name: null,
|
|
|
|
|
|
value: null,
|
|
|
|
|
|
defaults: null,
|
|
|
|
|
|
unit: null,
|
2026-02-08 22:31:13 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
2026-02-08 15:59:14 +08:00
|
|
|
|
|
|
|
|
|
|
const removeVariable = (row: ElementVariable) => {
|
|
|
|
|
|
if (currentElement.value && currentElement.value.variables) {
|
|
|
|
|
|
const filteredVars = currentElement.value.variables.filter(v => v.key !== row.key);
|
|
|
|
|
|
currentElement.value.variables = [...filteredVars];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const updateNode = () => {
|
|
|
|
|
|
if (currentNode.value && currentElement.value) {
|
|
|
|
|
|
// 深拷贝当前元素数据
|
2026-02-08 20:27:40 +08:00
|
|
|
|
const newElement = JSON.parse(JSON.stringify(currentElement.value)) as GraphTaskElement;
|
2026-03-26 23:36:47 +08:00
|
|
|
|
|
|
|
|
|
|
if (multiableParameters.value) {
|
|
|
|
|
|
newElement.parameters = groupedParameters.value.flatMap((group, groupIndex) => {
|
|
|
|
|
|
// 遍历每个分组,给组内所有参数统一设置/保持 groupIndex
|
|
|
|
|
|
return group.map(param => ({
|
|
|
|
|
|
...param,
|
|
|
|
|
|
groupIndex: groupIndex // 强制保证:当前参数的分组索引 = 所在分组的索引
|
|
|
|
|
|
}));
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 15:59:14 +08:00
|
|
|
|
// 更新节点数据
|
|
|
|
|
|
currentNode.value.replaceData(newElement);
|
|
|
|
|
|
// 触发事件通知父组件
|
|
|
|
|
|
emit('update-element', newElement);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-26 23:36:47 +08:00
|
|
|
|
const load = () => {
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-08 15:59:14 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => props.node,
|
|
|
|
|
|
(n?: Node | null | undefined) => resolveNode(n),
|
|
|
|
|
|
{ deep: true, immediate: true },
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-02-08 22:09:28 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => props.tree,
|
|
|
|
|
|
(n?: BehaviorTree | null | undefined) => {
|
|
|
|
|
|
currentTree.value = n ?? null;
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true, immediate: true },
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.treeEditing,
|
|
|
|
|
|
(n?: boolean | null | undefined) => {
|
|
|
|
|
|
treeEditing.value = n === true;
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true, immediate: true },
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-26 23:36:47 +08:00
|
|
|
|
watch(() => groupedParameters.value, () => updateNode(), { deep: true });
|
|
|
|
|
|
|
2026-02-08 15:59:14 +08:00
|
|
|
|
watch(() => currentElement.value, () => updateNode(), { deep: true });
|
|
|
|
|
|
|
2026-03-31 14:32:54 +08:00
|
|
|
|
watch(() => props.nodeCommands, (n: NodeCommand[] | null | undefined) => nodeCommands.value = n ?? [], { deep: true, immediate: true });
|
2026-03-27 00:05:04 +08:00
|
|
|
|
watch(() => props.platforms, (n: Platform[] | null | undefined) => platforms.value = n ?? [], { deep: true, immediate: true });
|
2026-04-13 17:26:50 +08:00
|
|
|
|
watch(() => props.subPlatforms, (n: Platform[] | null | undefined) => subPlatforms.value = n ?? [], { deep: true, immediate: true });
|
2026-03-27 00:05:04 +08:00
|
|
|
|
|
2026-02-08 15:59:14 +08:00
|
|
|
|
onMounted(() => load());
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
2026-03-31 14:32:54 +08:00
|
|
|
|
nodeCommands,
|
2026-03-27 00:05:04 +08:00
|
|
|
|
platforms,
|
2026-03-26 23:36:47 +08:00
|
|
|
|
addParameterTab,
|
|
|
|
|
|
groupedParametersActiveTab,
|
|
|
|
|
|
multiableParameters,
|
|
|
|
|
|
onEditParameterTab,
|
|
|
|
|
|
groupedParameters,
|
2026-04-13 17:26:50 +08:00
|
|
|
|
getPlatformTabName,
|
|
|
|
|
|
getAvailablePlatforms,
|
2026-02-08 15:59:14 +08:00
|
|
|
|
actionSpaceColumns,
|
|
|
|
|
|
activeTopTabsKey,
|
|
|
|
|
|
activeBottomTabsKey,
|
|
|
|
|
|
activeBottomTabs2Key,
|
|
|
|
|
|
currentElement,
|
|
|
|
|
|
addVariable,
|
|
|
|
|
|
removeVariable,
|
2026-02-08 22:09:28 +08:00
|
|
|
|
currentTree,
|
|
|
|
|
|
treeEditing,
|
2026-02-08 15:59:14 +08:00
|
|
|
|
// currentElementParameters,
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
2026-04-15 15:46:44 +08:00
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
2026-04-15 17:56:02 +08:00
|
|
|
|
.ks-location-title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: rgba(255, 255, 255, 0.65);
|
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.ks-location-item) {
|
|
|
|
|
|
.ant-form-item-row {
|
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.ant-form-item-label {
|
|
|
|
|
|
width: 48px;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
|
> label {
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.ant-form-item-control {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-15 15:46:44 +08:00
|
|
|
|
.ks-parameter-setting-tabs {
|
|
|
|
|
|
:deep(.ant-tabs-tab) {
|
|
|
|
|
|
width: 120px;
|
|
|
|
|
|
max-width: 120px;
|
|
|
|
|
|
}
|
|
|
|
|
|
:deep(.ant-tabs-tab-btn) {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|