Merge remote-tracking branch 'origin/master'

This commit is contained in:
MHW
2026-04-17 14:53:00 +08:00
6 changed files with 157 additions and 200 deletions

View File

@@ -1,94 +1,96 @@
<template> <template>
<a-dropdown <div>
:trigger="['contextmenu']" <a-dropdown
:getPopupContainer="getPopupContainer" :trigger="['contextmenu']"
@openChange="handleVisibleChange" :getPopupContainer="getPopupContainer"
> @openChange="handleVisibleChange"
<a-card
:class="[
'ks-scenario-node',
`ks-scenario-${element?.category ?? 'model'}-node`
]"
hoverable
> >
<template #title> <a-card
<a-space> :class="[
<span class="ks-scenario-node-title">{{ element?.description ?? element?.name ?? '-' }}</span> 'ks-scenario-node',
</a-space> `ks-scenario-${element?.category ?? 'model'}-node`
</template> ]"
hoverable
>
<template #title>
<a-space>
<span class="ks-scenario-node-title">{{ element?.description ?? element?.name ?? '-' }}</span>
</a-space>
</template>
<!-- 节点内容区域 --> <!-- 节点内容区域 -->
<div class="w-full"> <div class="w-full">
<div class="ks-scenario-node-content"> <div class="ks-scenario-node-content">
<div
v-for="(item, index) in element?.components || []"
:key="item.id || index"
class="ks-scenario-node-row"
>
<div <div
:data-port="`in-${item.id || index}`" v-for="(item, index) in element?.components || []"
:port="`in-${item.id || index}`" :key="item.id || index"
:title="`入桩: ${item.name}`" class="ks-scenario-node-row"
class="port port-in"
magnet="passive"
:data-item="JSON.stringify(item)"
> >
<div class="triangle-left"></div> <div
</div> :data-port="`in-${item.id || index}`"
:port="`in-${item.id || index}`"
:title="`入桩: ${item.name}`"
class="port port-in"
magnet="passive"
:data-item="JSON.stringify(item)"
>
<div class="triangle-left"></div>
</div>
<!-- child名称 --> <!-- child名称 -->
<div class="ks-scenario-node-name"> <div class="ks-scenario-node-name">
{{ substring(item.description ?? item.name, 20) }} {{ substring(item.description ?? item.name, 20) }}
</div> </div>
<!-- 右侧出桩只能作为连线源 --> <!-- 右侧出桩只能作为连线源 -->
<div <div
:data-port="`out-${item.id || index}`" :data-port="`out-${item.id || index}`"
:port="`out-${item.id || index}`" :port="`out-${item.id || index}`"
:title="`出桩: ${item.name}`" :title="`出桩: ${item.name}`"
class="port port-out" class="port port-out"
magnet="active" magnet="active"
:data-item="JSON.stringify(item)" :data-item="JSON.stringify(item)"
> >
<div class="triangle-right" ></div> <div class="triangle-right" ></div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </a-card>
</a-card>
<template #overlay> <template #overlay>
<a-menu @click="handleMenuClick"> <a-menu @click="handleMenuClick">
<a-sub-menu key="mount"> <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> <template #icon>
<CheckOutlined v-if="isTreeMounted(tree.id)" /> <LinkOutlined />
</template> </template>
{{ tree.name }} <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 />
</template>
删除
</a-menu-item> </a-menu-item>
<a-menu-item v-if="availableTrees.length === 0" disabled> </a-menu>
暂无可用行为树 </template>
</a-menu-item> </a-dropdown>
</a-sub-menu> </div>
<a-menu-divider />
<a-menu-item key="delete">
<template #icon>
<DeleteOutlined />
</template>
删除
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -99,7 +101,7 @@ import { DeleteOutlined, LinkOutlined, CheckOutlined, SettingOutlined } from '@a
import type { Graph } from '@antv/x6'; import type { Graph } from '@antv/x6';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { substring } from '@/utils/strings'; import { substring } from '@/utils/strings';
import { getAllBehaviorTreesBySceneId, updateBehaviorTree } from './api'; import { updateBehaviorTree } from './api';
import type { BehaviorTree } from '../designer/tree'; import type { BehaviorTree } from '../designer/tree';
export default defineComponent({ export default defineComponent({

View File

@@ -1,49 +1,53 @@
<template> <template>
<a-dropdown :trigger="['contextmenu']" @openChange="handleVisibleChange"> <div>
<a-card <a-dropdown :trigger="['contextmenu']" @openChange="handleVisibleChange">
:class="[
'ks-designer-node',
`ks-designer-${element?.category ?? 'model'}-node`, <a-card
`ks-designer-group-${element?.group ?? 'general'}` :class="[
]" 'ks-designer-node',
hoverable `ks-designer-${element?.category ?? 'model'}-node`,
> `ks-designer-group-${element?.group ?? 'general'}`
<template #title> ]"
<a-space> hoverable
<!-- <span class="ks-designer-node-icon"></span>--> >
<span class="ks-designer-node-title">{{ element?.name ?? '-' }}</span> <template #title>
</a-space> <a-space>
<!-- <span class="ks-designer-node-icon"></span>-->
<span class="ks-designer-node-title">{{ element?.name ?? '-' }}</span>
</a-space>
</template>
<div class="port port-in" data-port="in-0" magnet="passive">
<div class="triangle-left"></div>
</div>
<div class="w-full ks-designer-node-text">
<a-tooltip >
<template #title>
{{ element?.description ?? element?.name }}
</template>
<p class="ks-designer-node-label">
{{ substring(element?.name ?? (element?.name ?? '-'), 40) }}
</p>
</a-tooltip>
</div>
<div class="port port-out" data-port="out-0" magnet="active">
<div class="triangle-right" ></div>
</div>
</a-card>
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item key="delete">
<template #icon>
<DeleteOutlined />
</template>
删除
</a-menu-item>
</a-menu>
</template> </template>
</a-dropdown>
<div class="port port-in" data-port="in-0" magnet="passive"> </div>
<div class="triangle-left"></div>
</div>
<div class="w-full ks-designer-node-text">
<a-tooltip >
<template #title>
{{ element?.description ?? element?.name }}
</template>
<p class="ks-designer-node-label">
{{ substring(element?.name ?? (element?.name ?? '-'), 40) }}
</p>
</a-tooltip>
</div>
<div class="port port-out" data-port="out-0" magnet="active">
<div class="triangle-right" ></div>
</div>
</a-card>
<template #overlay>
<a-menu @click="handleMenuClick">
<a-menu-item key="delete">
<template #icon>
<DeleteOutlined />
</template>
删除
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@@ -154,23 +154,6 @@
<a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" /> <a-input v-else v-model:value="setting.defaultValue" :placeholder="setting.description" size="small" />
</a-form-item> </a-form-item>
</template> </template>
<template>
<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-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>
</template> </template>
</a-form> </a-form>
</template> </template>

View File

@@ -14,7 +14,7 @@
</div> </div>
<a-list :data-source="scenarios || []" size="small" style="min-height: 25vh"> <a-list :data-source="scenarios || []" size="small" style="min-height: 25vh">
<template #renderItem="{ item }"> <template #renderItem="{ item }">
<a-list-item @click="()=> handleSelect(item)"> <a-list-item :class="{ 'ks-item-selected': selectedId === item.id }" @click="()=> handleSelect(item)">
<a-flex> <a-flex>
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> <template #title>
@@ -23,20 +23,6 @@
</template> </template>
<span>{{ substring(item.name, 15) }}</span> <span>{{ substring(item.name, 15) }}</span>
</a-tooltip> </a-tooltip>
<a-flex class="ks-tree-actions">
<a-popconfirm
title="确定复制?"
@confirm="()=> handleCopy(item)"
>
<span class="ks-tree-action ks-tree-action-copy" @click.stop style="margin-right: 10px"><CopyOutlined /></span>
</a-popconfirm>
<a-popconfirm
title="确定删除?"
@confirm="()=> handleDelete(item)"
>
<span class="ks-tree-action ks-tree-action-delete" @click.stop><DeleteOutlined /></span>
</a-popconfirm>
</a-flex>
</a-flex> </a-flex>
</a-list-item> </a-list-item>
</template> </template>
@@ -74,6 +60,7 @@ export default defineComponent({
}); });
const activeKey = ref<number>(0); const activeKey = ref<number>(0);
const totalScenarios = ref<number>(0); const totalScenarios = ref<number>(0);
const selectedId = ref<number | null>(null);
const loadScenarios = (cb?: () => void) => { const loadScenarios = (cb?: () => void) => {
findScenarioByQuery(scenarioQuery.value).then(r => { findScenarioByQuery(scenarioQuery.value).then(r => {
@@ -89,19 +76,8 @@ export default defineComponent({
loadScenarios(); loadScenarios();
}; };
const handleDelete = (item: Scenario) => {
// TODO: 实现删除场景的API调用
console.log('删除场景:', item);
loadScenarios();
};
const handleCopy = (item: Scenario) => {
// TODO: 实现复制场景的API调用
console.log('复制场景:', item);
loadScenarios();
};
const handleSelect = (record: Scenario) => { const handleSelect = (record: Scenario) => {
selectedId.value = record.id;
emit('select-scenario', record); emit('select-scenario', record);
}; };
@@ -110,6 +86,7 @@ export default defineComponent({
onMounted(() => { onMounted(() => {
loadScenarios(() => { loadScenarios(() => {
if(scenarios.value.length > 0){ if(scenarios.value.length > 0){
selectedId.value = scenarios.value[0]!.id;
emit('select-scenario', scenarios.value[0]); emit('select-scenario', scenarios.value[0]);
} }
}); });
@@ -120,42 +97,18 @@ export default defineComponent({
totalScenarios, totalScenarios,
substring, substring,
activeKey, activeKey,
selectedId,
scenarios, scenarios,
scenarioQuery, scenarioQuery,
loadScenarios, loadScenarios,
handleSelect, handleSelect,
handleChange, handleChange,
handleDelete,
handleCopy,
}; };
}, },
}); });
</script> </script>
<style scoped> <style scoped>
.ks-tree-actions {
opacity: 0;
transition: opacity 0.3s;
}
.ks-tree-action {
cursor: pointer;
padding: 2px 5px;
border-radius: 3px;
}
.ks-tree-action:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.ks-tree-action-copy {
color: #1890ff;
}
.ks-tree-action-delete {
color: #ff4d4f;
}
.a-list-item { .a-list-item {
cursor: pointer; cursor: pointer;
padding: 8px 12px; padding: 8px 12px;
@@ -166,14 +119,16 @@ export default defineComponent({
background-color: #f5f5f5; background-color: #f5f5f5;
} }
.a-list-item:hover .ks-tree-actions {
opacity: 1;
}
.icon-scenario::before { .icon-scenario::before {
content: '\e6b8'; content: '\e6b8';
font-family: 'iconfont'; font-family: 'iconfont';
margin-right: 8px; margin-right: 8px;
color: #82c4e9; color: #82c4e9;
} }
:deep(.ant-list-item.ks-item-selected) {
background-color: rgba(130, 196, 233, 0.15);
border-left: 3px solid #82c4e9;
padding-left: 9px;
}
</style> </style>

View File

@@ -20,6 +20,7 @@ export interface BehaviorTree {
xmlContent: NullableString, xmlContent: NullableString,
graph: GraphContainer graph: GraphContainer
platformId: null | number platformId: null | number
scenarioId?: number,
} }
export interface BehaviorTreeRequest extends BehaviorTree { export interface BehaviorTreeRequest extends BehaviorTree {

View File

@@ -18,7 +18,7 @@
</div> </div>
<a-list :data-source="behaviorTrees || []" size="small" style="min-height: 25vh"> <a-list :data-source="behaviorTrees || []" size="small" style="min-height: 25vh">
<template #renderItem="{ item }"> <template #renderItem="{ item }">
<a-list-item @click="()=> handleSelect(item)"> <a-list-item :class="{ 'ks-item-selected': selectedId === item.id }" @click="()=> handleSelect(item)">
<a-flex> <a-flex>
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> <template #title>
@@ -86,10 +86,12 @@ export default defineComponent({
}); });
const activeKey = ref<number>(1); const activeKey = ref<number>(1);
const totalTress = ref<number>(0); const totalTress = ref<number>(0);
const selectedId = ref<number | null>(null);
watch( watch(
() => _props.scenarioId, () => _props.scenarioId,
() => { () => {
selectedId.value = null;
if (!_props.scenarioId) { if (!_props.scenarioId) {
behaviorTrees.value = []; behaviorTrees.value = [];
totalTress.value = 0; totalTress.value = 0;
@@ -139,6 +141,7 @@ export default defineComponent({
]; ];
const handleSelect = (record: BehaviorTree) => { const handleSelect = (record: BehaviorTree) => {
selectedId.value = record.id;
emit('select-tree', record); emit('select-tree', record);
}; };
@@ -157,6 +160,7 @@ export default defineComponent({
totalTress, totalTress,
substring, substring,
activeKey, activeKey,
selectedId,
behaviorTrees, behaviorTrees,
behaviorTreeQuery, behaviorTreeQuery,
loadTress, loadTress,
@@ -172,3 +176,11 @@ export default defineComponent({
</script> </script>
<style scoped>
:deep(.ant-list-item.ks-item-selected) {
background-color: rgba(130, 196, 233, 0.15);
border-left: 3px solid #82c4e9;
padding-left: 9px;
}
</style>