From 82fcedfa9724f11e0b85b5e38d23502024d1ee82 Mon Sep 17 00:00:00 2001 From: libertyspy Date: Sun, 8 Feb 2026 17:57:40 +0800 Subject: [PATCH] Initial commit --- modeler/src/views/decision/api.ts | 4 +- .../{types/node.ts => builder/element.ts} | 17 +- modeler/src/views/decision/builder/graph.ts | 71 +++-- modeler/src/views/decision/builder/hooks.ts | 58 ++-- modeler/src/views/decision/builder/line.ts | 47 +++ modeler/src/views/decision/builder/node.vue | 277 +++++++++++++----- modeler/src/views/decision/builder/ports.ts | 40 ++- modeler/src/views/decision/builder/props.ts | 4 +- .../src/views/decision/builder/register.ts | 23 +- modeler/src/views/decision/builder/utils.ts | 109 +++++++ modeler/src/views/decision/designer.vue | 74 +++-- modeler/src/views/decision/types/index.ts | 1 - modeler/src/views/decision/types/template.ts | 12 +- modeler/src/views/decision/types/tree.ts | 2 +- modeler/src/views/decision/utils/node.ts | 99 +------ 15 files changed, 548 insertions(+), 290 deletions(-) rename modeler/src/views/decision/{types/node.ts => builder/element.ts} (80%) create mode 100644 modeler/src/views/decision/builder/line.ts create mode 100644 modeler/src/views/decision/builder/utils.ts diff --git a/modeler/src/views/decision/api.ts b/modeler/src/views/decision/api.ts index 78bd4a9..4128393 100644 --- a/modeler/src/views/decision/api.ts +++ b/modeler/src/views/decision/api.ts @@ -29,11 +29,11 @@ export const findOneTreeById = (id: number): Promise): Promise => { - return req.postJson('/system/behaviortree/${id}', rt); + return req.postJson(`/system/behaviortree`, rt); }; export const createTree = (rt: Partial): Promise => { - return req.putJson('/system/behaviortree/${id}', rt); + return req.putJson(`/system/behaviortree`, rt); }; diff --git a/modeler/src/views/decision/types/node.ts b/modeler/src/views/decision/builder/element.ts similarity index 80% rename from modeler/src/views/decision/types/node.ts rename to modeler/src/views/decision/builder/element.ts index 50b169f..af077fa 100644 --- a/modeler/src/views/decision/types/node.ts +++ b/modeler/src/views/decision/builder/element.ts @@ -8,7 +8,20 @@ */ import type { NullableString } from '@/types'; -import type { NodeSetting } from './parameter'; +import type { NodeSetting } from '@/views/decision/types'; + +export interface DraggableElement { + id: number | null, + key?: NullableString, + name: NullableString, + description: NullableString, + category: NullableString, + draggable: boolean, + parent?: DraggableElement, + children: DraggableElement[] + [key: string]: unknown; +} + export type ElementStatus = 'default' | 'success' | 'failed' | 'running' | string | null @@ -51,6 +64,8 @@ export interface TaskNodeElement extends BaseElement { variables: ElementVariable[]; parameters: Record; + children?: TaskNodeElement[], + [key: string]: unknown; } diff --git a/modeler/src/views/decision/builder/graph.ts b/modeler/src/views/decision/builder/graph.ts index 4c2e9f0..55091f8 100644 --- a/modeler/src/views/decision/builder/graph.ts +++ b/modeler/src/views/decision/builder/graph.ts @@ -7,9 +7,10 @@ * that was distributed with this source code. */ -import { Clipboard, Edge, Graph, History, Keyboard, Path, Selection, Snapline, Transform } from '@antv/x6'; -import type { BaseElement } from '../types'; +import { Edge, Graph, Path, Selection } from '@antv/x6'; +import type { ModelElement } from './element'; import type { Connecting } from '@antv/x6/lib/graph/options'; +import {createLineOptions} from './line' Graph.registerConnector( 'sequenceFlowConnector', @@ -33,30 +34,30 @@ Graph.registerConnector( true, ); - export const createGraphConnectingAttributes = (): Partial => { + const lineOptions = createLineOptions(); return { snap: true, // 当 snap 设置为 true 时连线的过程中距离节点或者连接桩 50px 时会触发自动吸附 allowBlank: false, // 是否允许连接到画布空白位置的点,默认为 true allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 true highlight: true, // 当连接到节点时,通过 sourceAnchor 来指定源节点的锚点。 - connector: 'sequenceFlowConnector', - connectionPoint: 'boundary', // 指定连接点,默认值为 boundary。 + connector: 'smooth', + connectionPoint: 'anchor', // 指定连接点,默认值为 boundary。 anchor: 'center', - - router: 'manhattan', - // connector: { - // name: 'rounded', - // args: { - // radius: 8, - // }, - // }, - // connectionPoint: 'anchor', - // validateMagnet({ magnet }) { // return magnet.getAttribute('port-group') !== 'top' // }, // 验证连接 + createEdge(this: Graph) { + const edge: Edge = this.createEdge({ + shape: 'edge', + ...lineOptions, // 应用动画配置 + attrs: lineOptions.attrs, + animation: lineOptions.animation, + markup: lineOptions.markup, + }) + return edge; + }, validateConnection(this: Graph, { sourceCell, targetCell }) { console.error('validateConnection'); if (!sourceCell || !targetCell) return false; @@ -67,21 +68,28 @@ export const createGraphConnectingAttributes = (): Partial => { } // const sourceData = sourceCell.getData() as GraphElement; - const targetData = targetCell.getData() as BaseElement; + const targetData = targetCell.getData() as ModelElement; // 根节点不能作为子节点 - if (targetData.type === 'root') { + if (targetData.type === 'startEvent') { return false; } - // 检查是否已存在相同连接(保留原有逻辑) - const edges: Edge[] = this.getEdges(); - const existingConnection = edges.find(edge => - edge.getSourceCell() === sourceCell && - edge.getTargetCell() === targetCell, - ); + // 4. 新增核心逻辑:检查源节点是否已有出边(已连接其他节点) + // const hasOutgoingEdge = this.getOutgoingEdges(sourceCell); + // if (hasOutgoingEdge && hasOutgoingEdge.length > 1) { + // return false; + // } - return !existingConnection; + // 检查是否已存在相同连接 + // const edges: Edge[] = this.getEdges(); + // const existingConnection = edges.find(edge => + // edge.getSourceCell() === sourceCell && + // edge.getTargetCell() === targetCell, + // ); + // + // return !existingConnection; + return true; }, }; }; @@ -103,10 +111,10 @@ export const createGraphCanvas = (container: HTMLDivElement, readonly: boolean = }, mousewheel: { enabled: true, - zoomAtMousePosition: true, modifiers: 'ctrl', + factor: 1.1, + maxScale: 1.5, minScale: 0.5, - maxScale: 3, }, highlighting: { magnetAdsorbed: { @@ -162,16 +170,7 @@ export const createGraphCanvas = (container: HTMLDivElement, readonly: boolean = modifiers: 'shift', rubberband: true, }), - ).use( - new Transform({ - resizing: false, - rotating: false, - }), - ) - .use(new Snapline()) - .use(new Keyboard()) - .use(new Clipboard()) - .use(new History()); + ); return graph; diff --git a/modeler/src/views/decision/builder/hooks.ts b/modeler/src/views/decision/builder/hooks.ts index 71d7f38..dd5606d 100644 --- a/modeler/src/views/decision/builder/hooks.ts +++ b/modeler/src/views/decision/builder/hooks.ts @@ -8,10 +8,12 @@ */ import { computed, type ComputedRef, ref, type Ref } from 'vue'; -import { type Dom, Graph } from '@antv/x6'; +import { type Dom, Graph, Node } from '@antv/x6'; import type { NodeViewPositionEventArgs } from '@antv/x6/es/view/node/type'; import { createGraphCanvas } from './graph'; import { EventListener } from '@/utils/event'; +import type { ModelElement } from './element'; +// import {createLineOptions} from './line' export interface UseGraphCanvas { container: Ref; @@ -85,7 +87,7 @@ export const useGraphCanvas = (readonly: boolean = false): UseGraphCanvas => { const cells = graph.value?.getSelectedCells(); if (cells && cells.length) { graph.value?.removeCells(cells); - // 通知父节点更新状态 + // 通知父组件更新状态 emitGraphEvent('cells:removed', cells); } } @@ -104,7 +106,15 @@ export const useGraphCanvas = (readonly: boolean = false): UseGraphCanvas => { // 监听连线删除 graph.value.on('edge:removed', ({ edge }) => { - emitGraphEvent('cells:removed', edge); // 添加此行 + emitGraphEvent('edge:removed', edge); // 添加此行 + }); + + graph.value.on('edge:added', ({ edge }) => { + // const lineOptions = createLineOptions(); + // edge.setAttrs(lineOptions.attrs); + // edge.set(lineOptions.animation); + // edge.setMarkup(lineOptions.markup); + emitGraphEvent('edge:added', edge); // 添加此行 }); graph.value.on('edge:contextmenu', (ctx) => { @@ -132,28 +142,28 @@ export const useGraphCanvas = (readonly: boolean = false): UseGraphCanvas => { }); graph.value.on('edge:connected', ({ edge }) => { - // const sourceNode = edge.getSourceCell() as Node; - // const targetNode = edge.getTargetCell() as Node; + const sourceNode = edge.getSourceCell() as Node; + const targetNode = edge.getTargetCell() as Node; - // if (sourceNode && targetNode) { - // const sourceData = sourceNode.getData() as TaskNodeElement; - // const targetData = targetNode.getData() as TaskNodeElement; - // - // // 将连线存储到节点数据中 - // const sourceEdges = sourceData.edges || []; - // const existingEdge = sourceEdges.find(e => e.targetKey === targetNode.id); - // - // if (!existingEdge) { - // sourceEdges.push({ - // key: edge.id, - // sourceKey: sourceNode.id, - // sourceName: sourceData.name, - // targetKey: targetNode.id, - // targetName: targetData.name, - // }); - // sourceNode.setData({ ...sourceData, edges: sourceEdges }); - // } - // } + if (sourceNode && targetNode) { + const sourceData = sourceNode.getData() as ModelElement; + const targetData = targetNode.getData() as ModelElement; + + // 将连线存储到节点数据中 + const sourceEdges = sourceData.edges || []; + const existingEdge = sourceEdges.find(e => e.targetKey === targetNode.id); + + if (!existingEdge) { + sourceEdges.push({ + key: edge.id, + sourceKey: sourceNode.id, + sourceName: sourceData.name, + targetKey: targetNode.id, + targetName: targetData.name, + }); + sourceNode.replaceData({ ...sourceData, edges: sourceEdges }); + } + } edge.attr('line/stroke', '#3b82f6'); // 显式设置颜色 edge.attr('line/strokeWidth', 2); // 显式设置宽度 diff --git a/modeler/src/views/decision/builder/line.ts b/modeler/src/views/decision/builder/line.ts new file mode 100644 index 0000000..7ee5b4e --- /dev/null +++ b/modeler/src/views/decision/builder/line.ts @@ -0,0 +1,47 @@ +/* + * This file is part of the kernelstudio package. + * + * (c) 2014-2026 zlin + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +import { Shape } from '@antv/x6'; + +export const createLineOptions = (): any => { + return { + markup: [ + { + tagName: 'circle', + selector: 'marker', + attrs: { + stroke: 'none', + r: 3, + }, + }, + ...Shape.Edge.getMarkup() as any, + ], + attrs: { + line: { + stroke: '#5da0df', + strokeWidth: 2, + strokeDasharray: ' ', + strokeDashoffset: 0, + }, + marker: { + fill: '#5da0df', + atConnectionRatio: 0, + }, + }, + animation: [ + [ + { 'attrs/marker/atConnectionRatio': 1 }, + { + duration: 2000, + iterations: Infinity, + }, + ], + ], + } +} \ No newline at end of file diff --git a/modeler/src/views/decision/builder/node.vue b/modeler/src/views/decision/builder/node.vue index 4636e31..8282732 100644 --- a/modeler/src/views/decision/builder/node.vue +++ b/modeler/src/views/decision/builder/node.vue @@ -1,13 +1,74 @@