UPDATE: VERSION-20260315
This commit is contained in:
254
modeler/src/views/decision/graph/hooks.ts
Normal file
254
modeler/src/views/decision/graph/hooks.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* This file is part of the kernelstudio package.
|
||||
*
|
||||
* (c) 2014-2025 zlin <admin@kernelstudio.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE file
|
||||
* that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import { computed, type ComputedRef, ref, type Ref } from 'vue';
|
||||
import { type Dom, Graph, Node } from '@antv/x6';
|
||||
import type { NodeViewPositionEventArgs } from '@antv/x6/es/view/node/type';
|
||||
import { createGraphCanvas } from './canvas';
|
||||
import { EventListener } from '@/utils/event';
|
||||
import type { ModelElement } from './element';
|
||||
|
||||
// import {createLineOptions} from './line'
|
||||
|
||||
export interface UseGraphCanvas {
|
||||
container: Ref<HTMLDivElement | null>;
|
||||
readonly: boolean;
|
||||
eventListener: EventListener;
|
||||
currentZoom: Ref<number>;
|
||||
graph: ComputedRef<Graph>;
|
||||
zoomIn: () => void;
|
||||
zoomOut: () => void;
|
||||
fitToScreen: () => void;
|
||||
centerContent: () => void;
|
||||
resizeCanvas: () => void;
|
||||
handleGraphEvent: (name: string, fn: Function) => void;
|
||||
emitGraphEvent: (name: string, options?: any) => void;
|
||||
createCanvas: (c: HTMLDivElement) => Graph;
|
||||
}
|
||||
|
||||
export const useGraphCanvas = (readonly: boolean = false): UseGraphCanvas => {
|
||||
|
||||
const graph = ref<Graph | null>(null);
|
||||
const container = ref<HTMLDivElement | null>(null);
|
||||
const eventListener = new EventListener();
|
||||
const currentZoom = ref<number>(0);
|
||||
|
||||
const handleGraphEvent = (name: string, fn: Function) => {
|
||||
eventListener.on(name, fn);
|
||||
};
|
||||
|
||||
const emitGraphEvent = (name: string, options?: any): void => {
|
||||
eventListener.emit(name, options);
|
||||
};
|
||||
|
||||
const zoomIn = (): void => {
|
||||
if (graph.value) {
|
||||
const zoom = graph.value.zoom();
|
||||
graph.value.zoom(zoom + 0.1);
|
||||
}
|
||||
};
|
||||
|
||||
const zoomOut = (): void => {
|
||||
if (graph.value) {
|
||||
const zoom = graph.value.zoom();
|
||||
graph.value.zoom(Math.max(0.1, zoom - 0.1));
|
||||
}
|
||||
};
|
||||
|
||||
const fitToScreen = () => {
|
||||
if (graph.value) {
|
||||
graph.value.zoomToFit({ padding: 20 });
|
||||
}
|
||||
};
|
||||
|
||||
const centerContent = () => {
|
||||
if (graph.value) {
|
||||
graph.value.centerContent();
|
||||
}
|
||||
};
|
||||
|
||||
const resizeCanvas = () => {
|
||||
if (graph.value) {
|
||||
graph.value?.resize();
|
||||
}
|
||||
};
|
||||
|
||||
const initEvents = () => {
|
||||
if (!graph.value) {
|
||||
return;
|
||||
}
|
||||
graph.value.on('keydown', ({ e }: any) => {
|
||||
if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||
const cells = graph.value?.getSelectedCells();
|
||||
if (cells && cells.length) {
|
||||
graph.value?.removeCells(cells);
|
||||
// 通知父组件更新状态
|
||||
emitGraphEvent('cells:removed', cells);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
graph.value.on('scale', (ctx) => {
|
||||
currentZoom.value = ctx.sx;
|
||||
emitGraphEvent('scale', ctx);
|
||||
});
|
||||
|
||||
// 监听画布空白点击
|
||||
graph.value.on('blank:click', () => {
|
||||
graph.value?.cleanSelection();
|
||||
emitGraphEvent('blank:click');
|
||||
});
|
||||
|
||||
// 监听连线删除
|
||||
graph.value.on('edge: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) => {
|
||||
emitGraphEvent('edge:contextmenu', ctx);
|
||||
});
|
||||
|
||||
graph.value.on('edge:connect:abort', () => {
|
||||
// 当连线被拖拽到空白区域后释放,自动移除这条无效连线
|
||||
const edges = graph.value?.getEdges();
|
||||
const invalidEdges = (edges ?? []).filter(edge =>
|
||||
!edge.getSourceCell() || !edge.getTargetCell(),
|
||||
);
|
||||
if (invalidEdges.length) {
|
||||
graph.value?.removeCells(invalidEdges);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听连接失败
|
||||
graph.value.on('edge:connect:invalid', () => {
|
||||
emitGraphEvent('edge:connect:invalid');
|
||||
});
|
||||
|
||||
graph.value.on('edge:click', (edge) => {
|
||||
emitGraphEvent('edge:connect:invalid', edge);
|
||||
});
|
||||
|
||||
graph.value.on('edge:connected', ({ edge }) => {
|
||||
const sourceNode = edge.getSourceCell() as Node;
|
||||
const targetNode = edge.getTargetCell() as Node;
|
||||
|
||||
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.target === targetNode.id);
|
||||
|
||||
if (!existingEdge) {
|
||||
sourceEdges.push({
|
||||
id: sourceData.id,
|
||||
key: edge.id,
|
||||
source: sourceNode.id,
|
||||
sourceName: sourceData.name,
|
||||
connector: {},
|
||||
router: {},
|
||||
attrs: {},
|
||||
target: targetNode.id,
|
||||
targetName: targetData.name,
|
||||
});
|
||||
sourceNode.replaceData({ ...sourceData, edges: sourceEdges });
|
||||
}
|
||||
}
|
||||
|
||||
edge.attr('line/stroke', '#3b82f6'); // 显式设置颜色
|
||||
edge.attr('line/strokeWidth', 2); // 显式设置宽度
|
||||
// edge.refresh() // 刷新连线
|
||||
// graph.paint() // 重绘画布
|
||||
|
||||
emitGraphEvent('edge:connected', edge);
|
||||
});
|
||||
|
||||
graph.value.on('node:click', (ctx: NodeViewPositionEventArgs<Dom.ClickEvent>) => {
|
||||
console.info('node click', ctx);
|
||||
emitGraphEvent('node:click', ctx);
|
||||
});
|
||||
|
||||
// 监听节点选中事件
|
||||
graph.value.on('node:selected', ({ node }) => {
|
||||
console.info('node select', node);
|
||||
emitGraphEvent('node:selected', node);
|
||||
});
|
||||
|
||||
// 监听节点取消选中
|
||||
graph.value.on('node:unselected', () => {
|
||||
emitGraphEvent('node:unselected');
|
||||
});
|
||||
|
||||
// 监听节点鼠标移入,显示连接点
|
||||
graph.value.on('node:mouseenter', (ctx) => {
|
||||
emitGraphEvent('node:mouseenter', ctx);
|
||||
});
|
||||
|
||||
// 监听节点鼠标移出,隐藏连接点
|
||||
graph.value.on('node:mouseleave', (ctx) => {
|
||||
emitGraphEvent('node:mouseleave', ctx);
|
||||
});
|
||||
|
||||
// 监听节点状态变化
|
||||
graph.value.on('node:change:data', (ctx) => {
|
||||
const edges = graph.value?.getIncomingEdges(ctx.node);
|
||||
const { status } = ctx.node.getData();
|
||||
edges?.forEach((edge) => {
|
||||
if (status === 'running') {
|
||||
edge.attr('line/strokeDasharray', 5);
|
||||
edge.attr('line/style/animation', 'running-line 30s infinite linear');
|
||||
} else {
|
||||
edge.attr('line/strokeDasharray', '');
|
||||
edge.attr('line/style/animation', '');
|
||||
}
|
||||
});
|
||||
emitGraphEvent('node:change:data', ctx);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
const createCanvas = (c: HTMLDivElement): Graph => {
|
||||
container.value = c;
|
||||
graph.value = createGraphCanvas(c, readonly);
|
||||
initEvents();
|
||||
|
||||
emitGraphEvent('created', graph.value);
|
||||
|
||||
return graph.value as Graph;
|
||||
};
|
||||
|
||||
const graphInstance = computed(() => graph.value);
|
||||
|
||||
return {
|
||||
container,
|
||||
readonly,
|
||||
graph: graphInstance,
|
||||
eventListener,
|
||||
currentZoom,
|
||||
|
||||
emitGraphEvent,
|
||||
handleGraphEvent,
|
||||
|
||||
zoomIn,
|
||||
zoomOut,
|
||||
fitToScreen,
|
||||
centerContent,
|
||||
resizeCanvas,
|
||||
createCanvas,
|
||||
} as UseGraphCanvas;
|
||||
};
|
||||
Reference in New Issue
Block a user