新增装饰节点选中功能

This commit is contained in:
YHH
2025-06-18 18:31:29 +08:00
parent 5a06f5420b
commit aaa2a8ed2c
5 changed files with 123 additions and 19 deletions

View File

@@ -16,6 +16,7 @@ export function useAppState() {
const nodeTemplates_ = ref(nodeTemplates);
const treeNodes = ref<TreeNode[]>([]);
const selectedNodeId = ref<string | null>(null);
const selectedConditionNodeId = ref<string | null>(null); // 选中的条件节点ID
const nodeSearchText = ref('');
// 调试:检查条件节点模板
@@ -95,6 +96,7 @@ export function useAppState() {
nodeTemplates: nodeTemplates_,
treeNodes,
selectedNodeId,
selectedConditionNodeId,
nodeSearchText,
// 画布状态

View File

@@ -43,6 +43,7 @@ export function useBehaviorTreeEditor() {
appState.nodeSearchText,
appState.treeNodes,
appState.selectedNodeId,
appState.selectedConditionNodeId,
appState.checkingStatus,
appState.isInstalling,
appState.isInstalled,
@@ -456,6 +457,49 @@ export function useBehaviorTreeEditor() {
autoLayout,
validateTree,
clearAllConnections,
// 节点选择相关
selectNode: (nodeId: string) => {
// 选中普通节点时,取消条件节点的选中
appState.selectedNodeId.value = nodeId;
appState.selectedConditionNodeId.value = null;
console.log('🎯 选中节点:', nodeId);
},
selectConditionNode: (decoratorNode: any) => {
// 选中条件节点时,取消装饰器节点的选中
appState.selectedNodeId.value = null;
appState.selectedConditionNodeId.value = decoratorNode.id;
console.log('📝 选中条件节点进行编辑:', decoratorNode.attachedCondition?.name);
},
// 统一的属性更新方法(支持普通节点和条件节点)
updateNodeProperty: (path: string, value: any) => {
// 如果选中的是条件节点,更新装饰器节点的属性
if (appState.selectedConditionNodeId.value) {
const decoratorNode = appState.getNodeByIdLocal(appState.selectedConditionNodeId.value);
if (decoratorNode) {
// 使用通用方法更新属性
const keys = path.split('.');
let current: any = decoratorNode;
// 导航到目标属性的父对象
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
current[key] = {};
}
current = current[key];
}
// 设置最终值
const finalKey = keys[keys.length - 1];
current[finalKey] = value;
console.log('📝 更新条件属性:', path, '=', value);
}
} else {
// 普通节点属性更新
nodeOps.updateNodeProperty(path, value);
}
},
// 条件吸附功能
conditionDragState: conditionAttachment.dragState,
startConditionDrag: conditionAttachment.startConditionDrag,

View File

@@ -13,6 +13,7 @@ export function useComputedProperties(
nodeSearchText: Ref<string>,
treeNodes: Ref<TreeNode[]>,
selectedNodeId: Ref<string | null>,
selectedConditionNodeId: Ref<string | null>,
checkingStatus: Ref<boolean>,
isInstalling: Ref<boolean>,
isInstalled: Ref<boolean>,
@@ -79,6 +80,27 @@ export function useComputedProperties(
return node || null;
});
// 当前选中的条件节点(用于编辑条件属性)
const selectedConditionNode = computed(() => {
if (!selectedConditionNodeId.value) return null;
const decoratorNode = treeNodes.value.find(n => n.id === selectedConditionNodeId.value);
if (!decoratorNode || !decoratorNode.attachedCondition) return null;
// 创建一个虚拟的条件节点对象,用于属性编辑
return {
id: decoratorNode.id + '_condition',
name: decoratorNode.attachedCondition.name + '(条件)',
type: decoratorNode.attachedCondition.type,
icon: decoratorNode.attachedCondition.icon,
properties: decoratorNode.properties || {},
isConditionNode: true,
parentDecorator: decoratorNode
};
});
// 当前显示在属性面板的节点(普通节点或条件节点)
const activeNode = computed(() => selectedConditionNode.value || selectedNode.value);
// 根节点
const rootNode = () => {
return getRootNode(treeNodes.value);
@@ -140,6 +162,8 @@ export function useComputedProperties(
filteredConditionNodes,
filteredECSNodes,
selectedNode,
selectedConditionNode,
activeNode,
rootNode,
installStatusClass,
installStatusText,

View File

@@ -87,6 +87,21 @@
align-items: center;
gap: 6px;
font-size: 11px;
cursor: pointer;
padding: 4px;
border-radius: 3px;
transition: all 0.2s ease;
}
.condition-info:hover {
background: rgba(255, 215, 0, 0.2);
transform: scale(1.02);
}
.condition-info.condition-selected {
background: rgba(255, 215, 0, 0.3);
border: 1px solid #ffd700;
box-shadow: 0 0 0 2px rgba(255, 215, 0, 0.25);
}
.condition-icon {
@@ -99,6 +114,17 @@
font-weight: 500;
}
.edit-hint {
font-size: 10px;
color: #a0aec0;
margin-left: auto;
transition: color 0.2s ease;
}
.condition-info:hover .edit-hint {
color: #ffd700;
}
.remove-condition-btn {
background: none;
border: none;

View File

@@ -263,15 +263,21 @@
<!-- 条件装饰器的条件显示 -->
<div v-if="node.type === 'conditional-decorator'" class="condition-attachment-area">
<div v-if="node.attachedCondition" class="attached-condition">
<div class="condition-info">
<div
class="condition-info"
:class="{ 'condition-selected': selectedConditionNodeId === node.id }"
@click.stop="selectConditionNode(node)"
title="点击编辑条件属性"
>
<span class="condition-icon">{{ node.attachedCondition.icon }}</span>
<span class="condition-text">{{ getConditionDisplayText(node) }}</span>
<button
class="remove-condition-btn"
@click.stop="removeConditionFromDecorator(node)"
title="移除条件"
>×</button>
<span class="edit-hint">📝</span>
</div>
<button
class="remove-condition-btn"
@click.stop="removeConditionFromDecorator(node)"
title="移除条件"
>×</button>
</div>
<div v-else class="condition-placeholder">
<span class="placeholder-text">🎯 拖拽条件到此处</span>
@@ -346,32 +352,34 @@
<h3>⚙️ 属性面板</h3>
</div>
<div v-if="selectedNode" class="node-properties">
<div v-if="activeNode" class="node-properties">
<div class="property-section">
<h4>基本信息</h4>
<div class="property-item">
<label>节点名称:</label>
<input
type="text"
:value="selectedNode.name"
:value="activeNode.name"
@input="updateNodeProperty('name', $event.target.value)"
:key="selectedNode.id + '_name'"
:key="activeNode.id + '_name'"
:disabled="activeNode.isConditionNode"
>
</div>
<div class="property-item">
<label>描述:</label>
<textarea
:value="selectedNode.description"
:value="activeNode.description"
@input="updateNodeProperty('description', $event.target.value)"
:key="selectedNode.id + '_description'"
:key="activeNode.id + '_description'"
:disabled="activeNode.isConditionNode"
></textarea>
</div>
</div>
<div class="property-section" v-if="selectedNode.properties">
<div class="property-section" v-if="activeNode.properties">
<h4>节点属性</h4>
<div
v-for="(prop, key) in selectedNode.properties"
v-for="(prop, key) in activeNode.properties"
:key="key"
class="property-item"
>
@@ -381,21 +389,21 @@
type="text"
:value="prop.value"
@input="updateNodeProperty('properties.' + key + '.value', $event.target.value)"
:key="selectedNode.id + '_' + key + '_string'"
:key="activeNode.id + '_' + key + '_string'"
>
<input
v-else-if="prop.type === 'number'"
type="number"
:value="prop.value"
@input="updateNodeProperty('properties.' + key + '.value', parseFloat($event.target.value) || 0)"
:key="selectedNode.id + '_' + key + '_number'"
:key="activeNode.id + '_' + key + '_number'"
>
<input
v-else-if="prop.type === 'boolean'"
type="checkbox"
:checked="prop.value"
@change="updateNodeProperty('properties.' + key + '.value', $event.target.checked)"
:key="selectedNode.id + '_' + key + '_boolean'"
:key="activeNode.id + '_' + key + '_boolean'"
>
<textarea
v-else-if="prop.type === 'code'"
@@ -404,13 +412,13 @@
rows="6"
class="code-input"
placeholder="请输入代码..."
:key="selectedNode.id + '_' + key + '_code'"
:key="activeNode.id + '_' + key + '_code'"
></textarea>
<select
v-else-if="prop.type === 'select'"
:value="prop.value"
@change="updateNodeProperty('properties.' + key + '.value', $event.target.value)"
:key="selectedNode.id + '_' + key + '_select_' + prop.value"
:key="activeNode.id + '_' + key + '_select_' + prop.value"
>
<option
v-for="option in prop.options"
@@ -427,7 +435,7 @@
<div class="property-section">
<h4>节点配置</h4>
<pre class="config-preview">{{ selectedNode ? JSON.stringify(selectedNode, null, 2) : '{}' }}</pre>
<pre class="config-preview">{{ activeNode ? JSON.stringify(activeNode, null, 2) : '{}' }}</pre>
</div>
</div>