新增根节点
This commit is contained in:
@@ -8,6 +8,7 @@ import { useFileOperations } from './useFileOperations';
|
||||
import { useConnectionManager } from './useConnectionManager';
|
||||
import { useCanvasManager } from './useCanvasManager';
|
||||
import { useNodeDisplay } from './useNodeDisplay';
|
||||
import { validateTree as validateTreeStructure } from '../utils/nodeUtils';
|
||||
|
||||
/**
|
||||
* 主要的行为树编辑器组合功能
|
||||
@@ -248,32 +249,31 @@ export function useBehaviorTreeEditor() {
|
||||
|
||||
// 验证树结构
|
||||
const validateTree = () => {
|
||||
// 使用改进的验证函数
|
||||
const validationResult = validateTreeStructure(appState.treeNodes.value);
|
||||
|
||||
const errors: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
|
||||
const rootNodes = appState.treeNodes.value.filter(node =>
|
||||
!appState.treeNodes.value.some(otherNode =>
|
||||
otherNode.children?.includes(node.id)
|
||||
)
|
||||
);
|
||||
|
||||
if (rootNodes.length === 0) {
|
||||
errors.push('没有找到根节点');
|
||||
} else if (rootNodes.length > 1) {
|
||||
warnings.push(`找到多个根节点: ${rootNodes.map(n => n.name).join(', ')}`);
|
||||
if (!validationResult.isValid) {
|
||||
errors.push(validationResult.message);
|
||||
}
|
||||
|
||||
// 检查孤立节点(除了根节点)
|
||||
appState.treeNodes.value.forEach(node => {
|
||||
const hasParent = appState.treeNodes.value.some(otherNode =>
|
||||
otherNode.children?.includes(node.id)
|
||||
);
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
|
||||
if (!hasParent && !hasChildren && appState.treeNodes.value.length > 1) {
|
||||
warnings.push(`节点 "${node.name}" 是孤立节点`);
|
||||
if (node.type !== 'root') {
|
||||
const hasParent = appState.treeNodes.value.some(otherNode =>
|
||||
otherNode.children?.includes(node.id)
|
||||
);
|
||||
const hasChildren = node.children && node.children.length > 0;
|
||||
|
||||
if (!hasParent && !hasChildren && appState.treeNodes.value.length > 1) {
|
||||
warnings.push(`节点 "${node.name}" 是孤立节点`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 检查连接完整性
|
||||
appState.connections.value.forEach(conn => {
|
||||
const sourceNode = appState.treeNodes.value.find(n => n.id === conn.sourceId);
|
||||
const targetNode = appState.treeNodes.value.find(n => n.id === conn.targetId);
|
||||
@@ -286,7 +286,21 @@ export function useBehaviorTreeEditor() {
|
||||
}
|
||||
});
|
||||
|
||||
let message = '树结构验证完成!\n\n';
|
||||
// 检查节点类型一致性
|
||||
appState.treeNodes.value.forEach(node => {
|
||||
if (node.type === 'root' && node.parent) {
|
||||
errors.push(`根节点 "${node.name}" 不应该有父节点`);
|
||||
}
|
||||
|
||||
// 检查装饰器节点的限制
|
||||
if (node.type.includes('decorator') || node.type.includes('Decorator')) {
|
||||
if (node.children.length > 1) {
|
||||
warnings.push(`装饰器节点 "${node.name}" 建议只连接一个子节点,当前有 ${node.children.length} 个`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let message = '🔍 树结构验证完成!\n\n';
|
||||
|
||||
if (errors.length > 0) {
|
||||
message += `❌ 错误 (${errors.length}):\n${errors.map(e => `• ${e}`).join('\n')}\n\n`;
|
||||
@@ -297,7 +311,9 @@ export function useBehaviorTreeEditor() {
|
||||
}
|
||||
|
||||
if (errors.length === 0 && warnings.length === 0) {
|
||||
message += '✅ 没有发现问题!';
|
||||
message += '✅ 没有发现问题!树结构完全符合行为树规范。';
|
||||
} else if (errors.length === 0) {
|
||||
message += '✅ 树结构基本有效,但有一些建议优化的地方。';
|
||||
}
|
||||
|
||||
alert(message);
|
||||
|
||||
@@ -28,6 +28,13 @@ export function useComputedProperties(
|
||||
}
|
||||
) {
|
||||
// 过滤节点
|
||||
const filteredRootNodes = () => {
|
||||
return nodeTemplates.value.filter(node =>
|
||||
node.category === 'root' &&
|
||||
node.name.toLowerCase().includes(nodeSearchText.value.toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
const filteredCompositeNodes = () => {
|
||||
return nodeTemplates.value.filter(node =>
|
||||
node.category === 'composite' &&
|
||||
@@ -126,6 +133,7 @@ export function useComputedProperties(
|
||||
};
|
||||
|
||||
return {
|
||||
filteredRootNodes,
|
||||
filteredCompositeNodes,
|
||||
filteredDecoratorNodes,
|
||||
filteredActionNodes,
|
||||
|
||||
@@ -380,6 +380,28 @@ export function useConnectionManager(
|
||||
if (!parentNode || !parentNode.canHaveChildren) return false;
|
||||
if (!childNode || !childNode.canHaveParent) return false;
|
||||
|
||||
// 检查子节点数量限制
|
||||
if (parentNode.maxChildren !== undefined) {
|
||||
const currentChildrenCount = parentNode.children ? parentNode.children.length : 0;
|
||||
if (currentChildrenCount >= parentNode.maxChildren) {
|
||||
return false; // 已达到最大子节点数量
|
||||
}
|
||||
}
|
||||
|
||||
// 检查根节点限制:根节点不能有父节点
|
||||
if (childNode.type === 'root') {
|
||||
return false; // 根节点不能作为其他节点的子节点
|
||||
}
|
||||
|
||||
// 检查是否只能有一个根节点
|
||||
if (parentNode.type === 'root') {
|
||||
// 根节点只能连接一个子节点
|
||||
const rootNodes = treeNodes.value.filter(n => n.type === 'root');
|
||||
if (rootNodes.length > 1) {
|
||||
return false; // 不能有多个根节点
|
||||
}
|
||||
}
|
||||
|
||||
if (wouldCreateCycle(parentNodeId, childNodeId)) return false;
|
||||
if (isDescendant(childNodeId, parentNodeId)) return false;
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@ export interface NodeTemplate {
|
||||
type: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
category: 'composite' | 'decorator' | 'action' | 'condition' | 'ecs';
|
||||
category: 'composite' | 'decorator' | 'action' | 'condition' | 'ecs' | 'root';
|
||||
description: string;
|
||||
canHaveChildren: boolean;
|
||||
canHaveParent: boolean;
|
||||
maxChildren?: number; // 最大子节点数量限制
|
||||
minChildren?: number; // 最小子节点数量要求
|
||||
properties?: Record<string, PropertyDefinition>;
|
||||
className?: string; // 对应的实际类名
|
||||
namespace?: string; // 命名空间
|
||||
@@ -30,6 +32,21 @@ export interface NodeTemplate {
|
||||
* 基于项目实际行为树系统的节点模板定义
|
||||
*/
|
||||
export const nodeTemplates: NodeTemplate[] = [
|
||||
// 根节点
|
||||
{
|
||||
type: 'root',
|
||||
name: '根节点',
|
||||
icon: '🌳',
|
||||
category: 'root',
|
||||
description: '行为树的根节点,每棵树只能有一个根节点',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: false,
|
||||
maxChildren: 1,
|
||||
minChildren: 1,
|
||||
className: 'BehaviorTree',
|
||||
namespace: 'behaviourTree'
|
||||
},
|
||||
|
||||
// 复合节点 (Composites)
|
||||
{
|
||||
type: 'sequence',
|
||||
@@ -39,6 +56,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '按顺序执行子节点,任一失败则整体失败',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
minChildren: 1,
|
||||
className: 'Sequence',
|
||||
namespace: 'behaviourTree/composites',
|
||||
properties: {
|
||||
@@ -60,6 +78,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '按顺序执行子节点,任一成功则整体成功',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
minChildren: 1,
|
||||
className: 'Selector',
|
||||
namespace: 'behaviourTree/composites',
|
||||
properties: {
|
||||
@@ -81,6 +100,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '并行执行所有子节点',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
minChildren: 2,
|
||||
className: 'Parallel',
|
||||
namespace: 'behaviourTree/composites'
|
||||
},
|
||||
@@ -92,6 +112,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '并行执行子节点,任一成功则成功',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
minChildren: 2,
|
||||
className: 'ParallelSelector',
|
||||
namespace: 'behaviourTree/composites'
|
||||
},
|
||||
@@ -103,6 +124,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '随机顺序执行子节点,任一成功则成功',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
minChildren: 2,
|
||||
className: 'RandomSelector',
|
||||
namespace: 'behaviourTree/composites'
|
||||
},
|
||||
@@ -114,11 +136,12 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '随机顺序执行子节点,任一失败则失败',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
minChildren: 2,
|
||||
className: 'RandomSequence',
|
||||
namespace: 'behaviourTree/composites'
|
||||
},
|
||||
|
||||
// 装饰器节点 (Decorators)
|
||||
// 装饰器节点 (Decorators) - 只能有一个子节点
|
||||
{
|
||||
type: 'repeater',
|
||||
name: '重复器',
|
||||
@@ -127,6 +150,8 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '重复执行子节点指定次数或无限次',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
maxChildren: 1,
|
||||
minChildren: 1,
|
||||
className: 'Repeater',
|
||||
namespace: 'behaviourTree/decorators',
|
||||
properties: {
|
||||
@@ -154,6 +179,8 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '反转子节点的执行结果',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
maxChildren: 1,
|
||||
minChildren: 1,
|
||||
className: 'Inverter',
|
||||
namespace: 'behaviourTree/decorators'
|
||||
},
|
||||
@@ -165,6 +192,8 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '无论子节点结果如何都返回成功',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
maxChildren: 1,
|
||||
minChildren: 1,
|
||||
className: 'AlwaysSucceed',
|
||||
namespace: 'behaviourTree/decorators'
|
||||
},
|
||||
@@ -176,6 +205,8 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '无论子节点结果如何都返回失败',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
maxChildren: 1,
|
||||
minChildren: 1,
|
||||
className: 'AlwaysFail',
|
||||
namespace: 'behaviourTree/decorators'
|
||||
},
|
||||
@@ -187,6 +218,8 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '重复执行子节点直到成功',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
maxChildren: 1,
|
||||
minChildren: 1,
|
||||
className: 'UntilSuccess',
|
||||
namespace: 'behaviourTree/decorators'
|
||||
},
|
||||
@@ -198,6 +231,8 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '重复执行子节点直到失败',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
maxChildren: 1,
|
||||
minChildren: 1,
|
||||
className: 'UntilFail',
|
||||
namespace: 'behaviourTree/decorators'
|
||||
},
|
||||
@@ -209,6 +244,8 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '基于条件执行子节点',
|
||||
canHaveChildren: true,
|
||||
canHaveParent: true,
|
||||
maxChildren: 1,
|
||||
minChildren: 1,
|
||||
className: 'ConditionalDecorator',
|
||||
namespace: 'behaviourTree/decorators',
|
||||
properties: {
|
||||
@@ -222,7 +259,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
}
|
||||
},
|
||||
|
||||
// 动作节点 (Actions)
|
||||
// 动作节点 (Actions) - 叶子节点,不能有子节点
|
||||
{
|
||||
type: 'execute-action',
|
||||
name: '执行动作',
|
||||
@@ -231,6 +268,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '执行自定义代码逻辑',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'ExecuteAction',
|
||||
namespace: 'behaviourTree/actions',
|
||||
properties: {
|
||||
@@ -258,6 +296,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '等待指定时间后完成',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'WaitAction',
|
||||
namespace: 'behaviourTree/actions',
|
||||
properties: {
|
||||
@@ -285,6 +324,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '输出日志信息',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'LogAction',
|
||||
namespace: 'behaviourTree/actions',
|
||||
properties: {
|
||||
@@ -313,6 +353,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '运行另一个行为树',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'BehaviorTreeReference',
|
||||
namespace: 'behaviourTree/actions',
|
||||
properties: {
|
||||
@@ -326,7 +367,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
}
|
||||
},
|
||||
|
||||
// 条件节点 (基础条件)
|
||||
// 条件节点 (基础条件) - 叶子节点,不能有子节点
|
||||
{
|
||||
type: 'execute-conditional',
|
||||
name: '执行条件',
|
||||
@@ -335,6 +376,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '执行自定义条件判断',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'ExecuteActionConditional',
|
||||
namespace: 'behaviourTree/conditionals',
|
||||
properties: {
|
||||
@@ -348,7 +390,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
}
|
||||
},
|
||||
|
||||
// ECS专用节点
|
||||
// ECS专用节点 - 都是叶子节点
|
||||
{
|
||||
type: 'has-component',
|
||||
name: '检查组件',
|
||||
@@ -357,6 +399,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '检查实体是否包含指定组件',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'HasComponentCondition',
|
||||
namespace: 'ecs-integration/behaviors',
|
||||
properties: {
|
||||
@@ -377,6 +420,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '为实体添加组件',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'AddComponentAction',
|
||||
namespace: 'ecs-integration/behaviors',
|
||||
properties: {
|
||||
@@ -404,6 +448,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '从实体移除组件',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'RemoveComponentAction',
|
||||
namespace: 'ecs-integration/behaviors',
|
||||
properties: {
|
||||
@@ -424,6 +469,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '修改实体组件的属性',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'ModifyComponentAction',
|
||||
namespace: 'ecs-integration/behaviors',
|
||||
properties: {
|
||||
@@ -451,6 +497,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '检查实体是否具有指定标签',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'HasTagCondition',
|
||||
namespace: 'ecs-integration/behaviors',
|
||||
properties: {
|
||||
@@ -471,6 +518,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '检查实体是否处于激活状态',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'IsActiveCondition',
|
||||
namespace: 'ecs-integration/behaviors',
|
||||
properties: {
|
||||
@@ -491,6 +539,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: 'ECS优化的等待动作',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'WaitTimeAction',
|
||||
namespace: 'ecs-integration/behaviors',
|
||||
properties: {
|
||||
@@ -511,6 +560,7 @@ export const nodeTemplates: NodeTemplate[] = [
|
||||
description: '销毁当前实体',
|
||||
canHaveChildren: false,
|
||||
canHaveParent: true,
|
||||
maxChildren: 0,
|
||||
className: 'DestroyEntityAction',
|
||||
namespace: 'ecs-integration/behaviors'
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ export interface TreeNode {
|
||||
properties?: Record<string, PropertyDefinition>;
|
||||
canHaveChildren: boolean;
|
||||
canHaveParent: boolean;
|
||||
maxChildren?: number; // 最大子节点数量限制
|
||||
minChildren?: number; // 最小子节点数量要求
|
||||
hasError?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ export function createNodeFromTemplate(template: NodeTemplate, x: number = 100,
|
||||
properties: properties,
|
||||
canHaveChildren: template.canHaveChildren,
|
||||
canHaveParent: template.canHaveParent,
|
||||
maxChildren: template.maxChildren,
|
||||
minChildren: template.minChildren,
|
||||
hasError: false
|
||||
};
|
||||
return node;
|
||||
@@ -112,13 +114,81 @@ export function validateTree(nodes: TreeNode[]): ValidationResult {
|
||||
return { isValid: false, message: '行为树为空' };
|
||||
}
|
||||
|
||||
const root = getRootNode(nodes);
|
||||
if (!root) {
|
||||
// 检查根节点
|
||||
const rootNodes = nodes.filter(node => !node.parent);
|
||||
if (rootNodes.length === 0) {
|
||||
return { isValid: false, message: '缺少根节点' };
|
||||
}
|
||||
if (rootNodes.length > 1) {
|
||||
return { isValid: false, message: `发现多个根节点: ${rootNodes.map(n => n.name).join(', ')}` };
|
||||
}
|
||||
|
||||
// 可以添加更多验证逻辑
|
||||
// 例如:检查循环引用、孤立节点等
|
||||
// 验证每个节点的子节点数量限制
|
||||
for (const node of nodes) {
|
||||
const childrenCount = node.children.length;
|
||||
|
||||
// 检查最小子节点数量
|
||||
if (node.minChildren !== undefined && childrenCount < node.minChildren) {
|
||||
return {
|
||||
isValid: false,
|
||||
message: `节点 "${node.name}" 需要至少 ${node.minChildren} 个子节点,当前只有 ${childrenCount} 个`
|
||||
};
|
||||
}
|
||||
|
||||
// 检查最大子节点数量
|
||||
if (node.maxChildren !== undefined && childrenCount > node.maxChildren) {
|
||||
return {
|
||||
isValid: false,
|
||||
message: `节点 "${node.name}" 最多只能有 ${node.maxChildren} 个子节点,当前有 ${childrenCount} 个`
|
||||
};
|
||||
}
|
||||
|
||||
// 检查装饰器节点的特殊限制
|
||||
if (node.type.includes('decorator') || node.type.includes('Decorator')) {
|
||||
if (childrenCount !== 1) {
|
||||
return {
|
||||
isValid: false,
|
||||
message: `装饰器节点 "${node.name}" 必须有且只能有一个子节点,当前有 ${childrenCount} 个`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 检查叶子节点不能有子节点
|
||||
if (!node.canHaveChildren && childrenCount > 0) {
|
||||
return {
|
||||
isValid: false,
|
||||
message: `叶子节点 "${node.name}" 不能有子节点,但当前有 ${childrenCount} 个`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 检查循环引用
|
||||
const visited = new Set<string>();
|
||||
const recursionStack = new Set<string>();
|
||||
|
||||
function hasCycle(nodeId: string): boolean {
|
||||
if (recursionStack.has(nodeId)) return true;
|
||||
if (visited.has(nodeId)) return false;
|
||||
|
||||
visited.add(nodeId);
|
||||
recursionStack.add(nodeId);
|
||||
|
||||
const node = getNodeById(nodes, nodeId);
|
||||
if (node) {
|
||||
for (const childId of node.children) {
|
||||
if (hasCycle(childId)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
recursionStack.delete(nodeId);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
if (hasCycle(node.id)) {
|
||||
return { isValid: false, message: '检测到循环引用' };
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: true, message: '行为树结构有效' };
|
||||
}
|
||||
|
||||
@@ -190,6 +190,7 @@
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.node-item.root { border-left: 3px solid #f6ad55; }
|
||||
.node-item.composite { border-left: 3px solid #667eea; }
|
||||
.node-item.decorator { border-left: 3px solid #9f7aea; }
|
||||
.node-item.action { border-left: 3px solid #48bb78; }
|
||||
|
||||
@@ -47,6 +47,24 @@
|
||||
</div>
|
||||
|
||||
<div class="node-categories">
|
||||
<!-- 根节点 -->
|
||||
<div class="category">
|
||||
<h4 class="category-title">🌳 根节点</h4>
|
||||
<div class="node-list">
|
||||
<div
|
||||
v-for="node in filteredRootNodes()"
|
||||
:key="node.type"
|
||||
class="node-item root"
|
||||
:draggable="true"
|
||||
@dragstart="onNodeDragStart($event, node)"
|
||||
:title="node.description"
|
||||
>
|
||||
<span class="node-icon">{{ node.icon }}</span>
|
||||
<span class="node-name">{{ node.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 复合节点 -->
|
||||
<div class="category">
|
||||
<h4 class="category-title">🔗 复合节点</h4>
|
||||
|
||||
Reference in New Issue
Block a user