Files
auto-solution/modeler/src/views/decision/graph/hooks.ts

293 lines
8.0 KiB
TypeScript

/*
* 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, nextTick } 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;
destroyGraph: () => void;
clearGraph: () => 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 clearGraph = ()=> {
if (graph.value) {
try {
// graph.value.off();
graph.value.clearCells();
} catch (e) {
console.error('清空画布失败:', e);
}
}
}
const destroyGraph = ()=> {
eventListener.clear();
if (graph.value) {
clearGraph();
// 同步销毁 Graph 实例,避免异步导致的竞态条件
graph.value.dispose();
graph.value = null;
if (container.value) {
container.value.innerHTML = ''; // 清空容器内容
}
} else if (container.value) {
container.value.innerHTML = '';
}
}
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:dblclick', (ctx: NodeViewPositionEventArgs<Dom.ClickEvent>) => {
console.info('node:dblclick', ctx);
emitGraphEvent('node:dblclick', ctx);
});
// 监听节点选中事件
graph.value.on('node:selected', ({ node }) => {
console.info('node select', node,node.getData());
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,
destroyGraph,
clearGraph,
graph: graphInstance,
eventListener,
currentZoom,
emitGraphEvent,
handleGraphEvent,
zoomIn,
zoomOut,
fitToScreen,
centerContent,
resizeCanvas,
createCanvas,
} as UseGraphCanvas;
};