constructTreeUtil.ts 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. import { bpmnStart, bpmnTask, typeConfluence, typeTrigger } from '../config/variableName';
  2. import { NodeUtils } from './nodeUtil';
  3. import { changeTypeByTaskShape, hasGatewayType, typeConfig } from '../config';
  4. import { DEFAULT_CONNECT, DEFAULT_DISTANCE } from '../config/constants';
  5. // 定义 TreeNode 接口,表示树状数据结构中的节点
  6. export interface TreeNode {
  7. id: string;
  8. name: string;
  9. type: string;
  10. wnType: string;
  11. children: TreeNode[];
  12. virtualWidth: number; // 虚拟宽度(纵向排布)
  13. virtualHeight: number; // 虚拟高度(横向排布)
  14. isGateway?: boolean;
  15. level?: number;
  16. x: number;
  17. y: number;
  18. width: number;
  19. height: number;
  20. parentData?: any;
  21. offset?: any;
  22. subTree?: TreeNode; // 引用子树
  23. }
  24. type direction = 'vertical' | 'horizontal';
  25. // 封装为工具类来处理树状数据结构
  26. export class BPMNTreeBuilder {
  27. _allElement: any;
  28. _connectMap: any;
  29. constructor(allElement: any) {
  30. this._allElement = allElement;
  31. this._connectMap = new Map();
  32. }
  33. // 检查并添加不重复的子节点
  34. private addUniqueChild(parent: TreeNode, child: TreeNode): void {
  35. if (!parent.children.some(c => c.id === child.id)) parent.children.push(child);
  36. }
  37. public findStartElement(obj) {
  38. // 获取对象的键并遍历
  39. for (const key in obj) {
  40. if (obj.hasOwnProperty(key)) {
  41. const item = obj[key];
  42. if (item.type === bpmnStart) return item;
  43. }
  44. }
  45. return null; // 如果没有找到符合条件的对象,返回 null
  46. }
  47. public constructTree(treeType?: number): TreeNode {
  48. let startNode: any = this.findStartElement(this._allElement);
  49. if (!startNode) throw new Error('开始节点未找到');
  50. // 创建根节点
  51. const rootNode: TreeNode = {
  52. id: startNode.id,
  53. name: (startNode.businessObject as any).name,
  54. type: startNode.type,
  55. wnType: startNode.wnType,
  56. children: [],
  57. virtualWidth: 320,
  58. virtualHeight: DEFAULT_DISTANCE,
  59. level: 0,
  60. x: startNode.x,
  61. y: startNode.y,
  62. width: startNode.width,
  63. height: startNode.height,
  64. };
  65. // 构建连接关系
  66. let connections: Record<string, TreeNode[]> = {};
  67. let newConnectsMap = new Map();
  68. for (let key in this._allElement) {
  69. let element = this._allElement[key];
  70. // 如果某个节点有多个进线元素并且这些进线元素还有其它不同的出线元素 则 该元素的进线线条存在交叉的连接
  71. if (element.incoming?.length > 1) {
  72. let connectMap = new Map(); // 作用是过滤掉一些交叉的线条影响排序的样式
  73. element.incoming.map((item: any) => {
  74. if (!connectMap.has(item.id)) {
  75. let connectId = item.id;
  76. // 获取该元素的父元素 如果其父元素有多个子元素,则该connectId线条有出现交叉连接的情况
  77. let parentELement = item.source;
  78. if (parentELement.outgoing?.length > 1) {
  79. parentELement.outgoing.map((connect: any) => {
  80. if (!connectMap.has(connect.id)) {
  81. connectMap.set(connectId, parentELement.outgoing.length || 0);
  82. }
  83. });
  84. }
  85. }
  86. });
  87. if (treeType != 1) {
  88. // 过滤简流中的使用
  89. if (connectMap.size > 0) {
  90. if (connectMap.size === element.incoming?.length) {
  91. // 删除value最小的
  92. let minKey = null;
  93. let minValue = Infinity;
  94. for (let [key, value] of connectMap) {
  95. if (value < minValue) {
  96. minValue = value;
  97. minKey = key;
  98. }
  99. }
  100. // 如果找到了最小值的键,则删除该键值对
  101. if (minKey !== null) {
  102. connectMap.delete(minKey);
  103. }
  104. }
  105. }
  106. connectMap.forEach((value, key) => {
  107. newConnectsMap.set(key, value);
  108. });
  109. }
  110. }
  111. }
  112. for (let key in this._allElement) {
  113. let element = this._allElement[key];
  114. if (element.type === 'bpmn:SequenceFlow' && !newConnectsMap.has(element.id)) {
  115. let sourceId = element.source.id;
  116. let targetId = element.target.id;
  117. if (!connections[sourceId]) connections[sourceId] = [];
  118. let targetElement = element.target;
  119. let childNode: TreeNode = {
  120. id: targetId,
  121. name: targetElement.name,
  122. type: targetElement.type,
  123. wnType: targetElement.wnType,
  124. children: [],
  125. virtualWidth: 320,
  126. virtualHeight: DEFAULT_DISTANCE,
  127. x: targetElement.x,
  128. y: targetElement.y,
  129. width: targetElement.width,
  130. height: targetElement.height,
  131. };
  132. if (!connections[sourceId].some(child => child.id === childNode.id)) connections[sourceId].push(childNode);
  133. }
  134. }
  135. // 使用广度优先遍历构建树
  136. let queue: TreeNode[] = [rootNode];
  137. let processedNodes = new Map(); // 跟踪处理过的节点
  138. while (queue.length > 0) {
  139. let current = queue.shift();
  140. if (current && connections[current.id]) {
  141. connections[current.id].forEach(child => {
  142. child.parentData = current;
  143. if (!processedNodes.has(child.id)) {
  144. this.addUniqueChild(current, child); // 添加到父节点
  145. queue.push(child); // 推入队列
  146. processedNodes.set(child.id, child); // 标记为已处理
  147. } else this.addUniqueChild(current, processedNodes.get(child.id)); // 添加到父节点
  148. });
  149. }
  150. }
  151. this._connectMap = newConnectsMap;
  152. return rootNode;
  153. }
  154. /**
  155. * 使用栈进行深度优先遍历,计算虚拟宽度
  156. * @type 0:自研, 1:简流
  157. * */
  158. public calculateVirtualWidth(root: TreeNode, elementRegistry: any): number {
  159. // 栈存储的是节点和已计算的子节点总宽度
  160. let stack: { node: TreeNode; totalWidth: number }[] = [];
  161. stack.push({ node: root, totalWidth: 0 });
  162. // 遍历过程中保存父子关系的映射
  163. let parentChildMapping = new Map<TreeNode, TreeNode[]>();
  164. while (stack.length > 0) {
  165. let current = stack[stack.length - 1];
  166. let { node, totalWidth } = current;
  167. // 如果节点没有子节点,则直接设置默认宽度
  168. if (node.children.length === 0) {
  169. node.virtualWidth = 320;
  170. stack.pop(); // 从栈中移除
  171. this.updateParent(stack, node.virtualWidth!, 'horizontal');
  172. continue;
  173. }
  174. // 如果子节点还没有完全遍历,则将子节点压入栈
  175. let children = node.children;
  176. let unprocessedChildren = parentChildMapping.get(node);
  177. if (!unprocessedChildren) {
  178. unprocessedChildren = [...children]; // 克隆子节点列表
  179. parentChildMapping.set(node, unprocessedChildren);
  180. }
  181. if (unprocessedChildren.length > 0) {
  182. let child = unprocessedChildren.pop(); // 取出一个未处理的子节点
  183. stack.push({ node: child!, totalWidth: 0 });
  184. } else {
  185. // 所有子节点都已处理完毕,计算虚拟宽度
  186. let finalWidth = totalWidth;
  187. let hasMergeChild = children.some(child => child.wnType === typeConfluence);
  188. let hasTrigger = children.some(child => child.wnType === typeTrigger);
  189. if (hasMergeChild && !hasTrigger) finalWidth = 320;
  190. else {
  191. let newElement = elementRegistry?.get(node.id);
  192. finalWidth = children.reduce((sum, child) => {
  193. let virtualWidth = child.virtualWidth ?? 0;
  194. if (child.isGateway) virtualWidth = 320;
  195. return sum + virtualWidth;
  196. }, 0);
  197. let hasTrigger = newElement.outgoing?.some(o => o.target.wnType === typeTrigger);
  198. let isNotTriggerChildren = children?.some(o => o.wnType != typeTrigger);
  199. if (hasTrigger && !isNotTriggerChildren) finalWidth += 320;
  200. }
  201. // 重新计算父元素为分流网关元素的虚拟宽度 该元素的虚拟宽度需要获取到该分流到合流内的所有分流虚拟宽度 取最大值(过滤chidren包含触发节点的元素)
  202. if (hasGatewayType.has(node.parentData?.wnType) && !hasTrigger) {
  203. // 辅助函数,用于递归遍历路径
  204. let elements: any = new Map();
  205. let number: any = 0; // 取最大值
  206. function getChildrenMaxWidth(targetElement) {
  207. elements.set(targetElement.id, targetElement.virtualWidth);
  208. if (number < targetElement.virtualWidth && targetElement.wnType != typeConfluence) number = targetElement.virtualWidth;
  209. if (targetElement.id !== node.parentData?.id + '_confluence') findPath(targetElement, false);
  210. }
  211. function findPath(currentElement, isRoot: boolean, id?) {
  212. currentElement?.children.forEach(targetElement => {
  213. if (isRoot) {
  214. if (targetElement.id === id) getChildrenMaxWidth(targetElement);
  215. } else getChildrenMaxWidth(targetElement);
  216. });
  217. }
  218. findPath(node.parentData, true, node.id);
  219. finalWidth = number;
  220. }
  221. // 如果该节点有多个incoming线 则 将该节点默认为是合流节点 将它宽度设置为320
  222. if (elementRegistry) {
  223. let currentElement = elementRegistry.get(node.id);
  224. if (currentElement.incoming?.length > 1) node.isGateway = true;
  225. }
  226. node.virtualWidth = finalWidth;
  227. stack.pop(); // 从栈中移除
  228. this.updateParent(stack, finalWidth, 'horizontal');
  229. }
  230. }
  231. return root.virtualWidth!;
  232. }
  233. public calculateVirtualHeight(root: TreeNode, elementRegistry?: any): number {
  234. // 栈存储的是节点和已计算的子节点总高度
  235. let stack: { node: TreeNode; totalHeight: number }[] = [];
  236. stack.push({ node: root, totalHeight: 0 });
  237. // 遍历过程中保存父子关系的映射
  238. let parentChildMapping = new Map<TreeNode, TreeNode[]>();
  239. while (stack.length > 0) {
  240. let current = stack[stack.length - 1];
  241. let { node, totalHeight } = current;
  242. // 如果节点没有子节点,则直接设置默认高度
  243. if (node.children.length === 0) {
  244. node.virtualHeight = 118;
  245. stack.pop(); // 从栈中移除
  246. this.updateParent(stack, node.virtualHeight!, 'vertical');
  247. continue;
  248. }
  249. // 如果子节点还没有完全遍历,则将子节点压入栈
  250. let children = node.children;
  251. let unprocessedChildren = parentChildMapping.get(node);
  252. if (!unprocessedChildren) {
  253. unprocessedChildren = [...children]; // 克隆子节点列表
  254. parentChildMapping.set(node, unprocessedChildren);
  255. }
  256. if (unprocessedChildren.length > 0) {
  257. let child = unprocessedChildren.pop(); // 取出一个未处理的子节点
  258. stack.push({ node: child!, totalHeight: 0 });
  259. } else {
  260. // 所有子节点都已处理完毕,计算虚拟高度
  261. let finalHeight = totalHeight;
  262. let hasMergeChild = children.some(child => child.wnType === typeConfluence);
  263. if (hasMergeChild) finalHeight = 208;
  264. else
  265. finalHeight = children.reduce((sum, child) => {
  266. let virtualHeight = child.virtualHeight ?? 0;
  267. if (child.isGateway) virtualHeight = 208;
  268. return sum + virtualHeight;
  269. }, 0);
  270. // 重新计算父元素为分流网关元素的虚拟高度 该元素的虚拟高度需要获取到该分流到合流内的所有分流虚拟高度 取最大值
  271. if (hasGatewayType.has(node.parentData?.wnType)) {
  272. // 辅助函数,用于递归遍历路径
  273. let elements: any = new Map();
  274. let number: any = 0; // 取最大值
  275. function getChildrenMaxHeight(targetElement) {
  276. elements.set(targetElement.id, targetElement.virtualHeight);
  277. if (number < targetElement.virtualHeight && targetElement.wnType != typeConfluence) number = targetElement.virtualHeight;
  278. if (targetElement.id !== node.parentData?.id + '_confluence') findPath(targetElement, false);
  279. }
  280. function findPath(currentElement, isRoot: boolean, id?) {
  281. currentElement?.children.forEach(targetElement => {
  282. if (isRoot) {
  283. if (targetElement.id === id) getChildrenMaxHeight(targetElement);
  284. } else getChildrenMaxHeight(targetElement);
  285. });
  286. }
  287. findPath(node.parentData, true, node.id);
  288. finalHeight = number;
  289. }
  290. // 如果该节点有多个incoming线 则 将该节点默认为是合流节点 将它高度设置为320
  291. if (elementRegistry) {
  292. let currentElement = elementRegistry.get(node.id);
  293. if (currentElement.incoming?.length > 1) node.isGateway = true;
  294. }
  295. node.virtualHeight = finalHeight;
  296. stack.pop(); // 从栈中移除
  297. this.updateParent(stack, finalHeight, 'vertical');
  298. }
  299. }
  300. return root.virtualHeight!;
  301. }
  302. // 更新栈顶的父节点的 totalWidth
  303. private updateParent(stack: { node: TreeNode }[], number: number, type: 'horizontal' | 'vertical'): void {
  304. if (stack.length > 0) {
  305. const parent = stack[stack.length - 1];
  306. type === 'horizontal' ? (parent['totalWidth'] += number) : (parent['totalHeight'] += number);
  307. }
  308. }
  309. // 根据 ID 查询树中对应的节点,并返回其虚拟宽度
  310. public findNodeById(root: TreeNode, id: string): TreeNode | undefined {
  311. if (root.id === id) return root;
  312. for (const child of root.children) {
  313. const foundNode = this.findNodeById(child, id);
  314. if (foundNode) return foundNode; // 返回找到的节点
  315. }
  316. return undefined; // 如果找不到匹配的节点
  317. }
  318. // 判断是否为网关
  319. public isGateway(element) {
  320. const gatewayTypes = ['bpmn:ExclusiveGateway', 'bpmn:ParallelGateway', 'bpmn:InclusiveGateway', 'bpmn:EventBasedGateway'];
  321. if (element.wnType === typeConfluence) return false;
  322. return gatewayTypes.includes(element.type);
  323. }
  324. public formatCanvas(visited, modeling, elementRegistry) {
  325. let obj = visited.reduce((acc, item) => {
  326. let x = item.offset?.x - item.x || 0;
  327. if (!acc[x]) acc[x] = [];
  328. acc[x].push(elementRegistry.get(item.id));
  329. return acc;
  330. }, {});
  331. // 分组移动 优化性能
  332. for (const key in obj) {
  333. if (obj.hasOwnProperty(key)) {
  334. let list = obj[key];
  335. modeling.moveElements(list, { x: Number(key), y: 0 });
  336. }
  337. }
  338. }
  339. public formatCanvasHorizontal(visited, modeling, elementRegistry) {
  340. let obj = visited.reduce((acc, item) => {
  341. let y = item.offset?.y - item.y || 0;
  342. if (!acc[y]) acc[y] = [];
  343. acc[y].push(elementRegistry.get(item.id));
  344. return acc;
  345. }, {});
  346. // 分组移动 优化性能
  347. for (const key in obj) obj.hasOwnProperty(key) && modeling.moveElements(obj[key], { x: 0, y: Number(key) });
  348. }
  349. public getParentOffsetById(data: any, id: string) {
  350. if (data.parentData) {
  351. if (data.parentData.id === id) {
  352. // 如果合流宽度为0 则x轴宽度和合流网关一致
  353. let offset = {
  354. x: data.parentData.offset.x + data.parentData.width / 2,
  355. y: data.parentData.offset.y,
  356. };
  357. return offset;
  358. }
  359. return this.getParentOffsetById(data.parentData, id);
  360. }
  361. }
  362. // 广度优先遍历树状结构
  363. public traverseTreeBFS(root: TreeNode, callback: (node: TreeNode) => void): void {
  364. let queue: TreeNode[] = [root]; // 初始化队列
  365. const processedNodes = new Set<string>(); // 用于跟踪已处理节点
  366. const AUTO_HEIGHT = 150;
  367. // 获取开始节点的虚拟高度
  368. while (queue.length > 0) {
  369. let current = queue.shift(); // 从队列中取出第一个元素
  370. if (current && !processedNodes.has(current.id)) {
  371. processedNodes.add(current.id); // 标记为已处理
  372. // 对开始节点外的元素进行偏移
  373. if (current.id != root.id) {
  374. let parentData = current.parentData;
  375. let n = 0;
  376. if (parentData) {
  377. // 遍历数组,遇到条件终止
  378. for (let i = 0; i < parentData.children.length; i++) {
  379. if (parentData.children[i].id === current.id) break;
  380. if (parentData.children[i].wnType === typeConfluence) {
  381. parentData.children[i].virtualWidth = 320;
  382. }
  383. n += parentData.children[i].virtualWidth;
  384. }
  385. }
  386. let parentX = parentData.offset ? parentData.offset.x : parentData.x;
  387. let parentY = parentData.offset ? parentData.offset.y : parentData.y;
  388. // X轴坐标边界
  389. let minX = parentX - parentData.virtualWidth / 2;
  390. let currentVirtualWidth = current.virtualWidth / 2;
  391. if (current.children)
  392. if (parentData.virtualWidth > current.virtualWidth && parentData.children?.length === 1) currentVirtualWidth = parentData.virtualWidth / 2;
  393. // 如果某个节点children不包含除typeTrigger外的元素
  394. if (parentData.children?.length === 1 && !parentData.children.some(o => o.wnType != typeTrigger)) {
  395. minX = parentX - (parentData.virtualWidth + 320) / 2;
  396. }
  397. // 如果parentData.children 只含有触发节点(任务节点连接合流节点)
  398. let isTrigger = parentData.children.every(s => s.wnType === typeTrigger);
  399. if (isTrigger) {
  400. n += 320;
  401. }
  402. let offset = {
  403. x: minX + currentVirtualWidth - (current.width - parentData.width) / 2 + n,
  404. y: parentY + AUTO_HEIGHT + current.height,
  405. };
  406. if (current.id.includes('_confluence')) {
  407. let id = current.id.replace('_confluence', '');
  408. let gatewayOffset = this.getParentOffsetById(current, id);
  409. offset = {
  410. x: gatewayOffset.x,
  411. y: parentY + AUTO_HEIGHT + current.height,
  412. };
  413. }
  414. current.offset = offset;
  415. }
  416. callback(current); // 执行操作
  417. current.children = current.children.map((children: any) => {
  418. return { ...children, parentData: { ...current } };
  419. });
  420. queue.push(...current.children); // 将子节点添加到队列中
  421. }
  422. }
  423. }
  424. // 广度优先遍历树状结构
  425. public bpmnTraverseTreeBFS(root: TreeNode, callback: (node: TreeNode) => void, type: direction): void {
  426. let queue: TreeNode[] = [root]; // 初始化队列
  427. const AUTO_HEIGHT = 150;
  428. // 获取开始节点的虚拟高度
  429. while (queue.length > 0) {
  430. let current = queue.shift(); // 从队列中取出第一个元素
  431. if (current) {
  432. // 对开始节点外的元素进行偏移
  433. if (current.id != root.id) {
  434. let parentData = current.parentData;
  435. let n = 0;
  436. if (parentData) {
  437. // 遍历数组,遇到条件终止
  438. for (let i = 0; i < parentData.children.length; i++) {
  439. if (parentData.children[i].id === current.id) break;
  440. type === 'horizontal' ? (n += parentData.children[i].virtualHeight || 208) : (n += parentData.children[i].virtualWidth || 320);
  441. }
  442. }
  443. let parentX = parentData.offset ? parentData.offset.x : parentData.x;
  444. let parentY = parentData.offset ? parentData.offset.y : parentData.y;
  445. // X轴坐标边界
  446. if (type === 'horizontal') {
  447. let minY = parentY - parentData.virtualHeight / 2;
  448. let currentVirtualHeight = current.virtualHeight / 2;
  449. if (parentData.virtualHeight > current.virtualHeight && parentData.children?.length === 1) currentVirtualHeight = parentData.virtualHeight / 2;
  450. let offset = {
  451. x: parentX + AUTO_HEIGHT + current.width,
  452. y: minY + n + currentVirtualHeight + (parentData.height - current.height) / 2,
  453. };
  454. let level = current.level;
  455. current.offset = offset;
  456. current.level = level;
  457. } else {
  458. let minX = parentX - parentData.virtualWidth / 2;
  459. let currentVirtualWidth = current.virtualWidth / 2;
  460. if (parentData.virtualWidth > current.virtualWidth && parentData.children?.length === 1) currentVirtualWidth = parentData.virtualWidth / 2;
  461. let offset = {
  462. x: minX + n + currentVirtualWidth + (parentData.width - current.width) / 2,
  463. y: parentY + AUTO_HEIGHT + current.height,
  464. };
  465. let level = current.level;
  466. current.offset = offset;
  467. current.level = level;
  468. }
  469. }
  470. callback(current); // 执行操作
  471. let level = current?.level ?? 0;
  472. current.children = current.children.map((children: any) => {
  473. return { ...children, parentData: { ...current }, level: level + 1 };
  474. });
  475. queue.push(...current.children); // 将子节点添加到队列中
  476. }
  477. }
  478. }
  479. // 修改线条坐标
  480. public updateConnectionWaypoints(connect: any, modeling: any, type: direction) {
  481. let source = connect.source;
  482. let target = connect.target;
  483. let newWaypoints: any = [];
  484. if (type === 'vertical') {
  485. if (source.x < target.x) {
  486. newWaypoints = [
  487. { x: source.x + source.width / 2, y: source.y + source.height },
  488. { x: source.x + source.width / 2, y: target.y - 60 },
  489. { x: target.x + target.width / 2, y: target.y - 60 },
  490. { x: target.x + target.width / 2, y: target.y },
  491. ];
  492. } else if (source.x > target.x) {
  493. newWaypoints = [
  494. { x: source.x + source.width / 2, y: source.y + source.height },
  495. { x: source.x + source.width / 2, y: target.y - 60 },
  496. { x: target.x + target.width / 2, y: target.y - 60 },
  497. { x: target.x + target.width / 2, y: target.y },
  498. ];
  499. } else {
  500. newWaypoints = [
  501. { x: source.x + source.width / 2, y: source.y + source.height },
  502. { x: target.x + target.width / 2, y: target.y },
  503. ];
  504. }
  505. } else {
  506. // 横向
  507. if (source.y < target.y) {
  508. newWaypoints = [
  509. { x: source.x + source.width, y: source.y + source.height / 2 },
  510. { x: target.x - DEFAULT_CONNECT / 2, y: source.y + source.height / 2 },
  511. { x: target.x - DEFAULT_CONNECT / 2, y: target.y + target.height / 2 },
  512. { x: target.x, y: target.y + target.height / 2 },
  513. ];
  514. } else if (source.y > target.y) {
  515. newWaypoints = [
  516. { x: source.x + source.width, y: source.y + source.height / 2 },
  517. { x: target.x - DEFAULT_CONNECT / 2, y: source.y + source.height / 2 },
  518. { x: target.x - DEFAULT_CONNECT / 2, y: target.y + target.height / 2 },
  519. { x: target.x, y: target.y + target.height / 2 },
  520. ];
  521. } else {
  522. newWaypoints = [
  523. { x: source.x + source.width, y: source.y + source.height / 2 },
  524. { x: target.x, y: target.y + target.height / 2 },
  525. ];
  526. }
  527. }
  528. modeling.updateWaypoints(connect, newWaypoints);
  529. }
  530. // 判断元素与网关之间的垂直距离是否小于某个阈值
  531. public isWithinThresholdDel(target, source, threshold) {
  532. // 这里假设网关在上方,即网关的 y 坐标小于当前元素的 y 坐标
  533. let gatewayY = target.y;
  534. let sourceElementY = source.y;
  535. // 如果是合流节点 获取其所有的上一个节点 判断上一个节点的y轴最大值
  536. if (target.wnType === typeConfluence) {
  537. if (target.incoming?.length > 1) {
  538. let y = -Infinity;
  539. target.incoming.map((item: any) => {
  540. if (item?.source?.y > y) y = item.source.y;
  541. });
  542. return gatewayY - y <= threshold;
  543. }
  544. }
  545. return gatewayY - sourceElementY < threshold && gatewayY > sourceElementY;
  546. }
  547. public moveConnectedElements(connection: any, height?: any) {
  548. const stack: any = []; // 用于存储待处理的连接线
  549. const processedElements = new Set(); // 记录已经处理过的目标元素
  550. stack.push(connection); // 从给定的连接线开始
  551. while (stack.length > 0) {
  552. const currentConnection: any = stack.pop();
  553. const target = currentConnection.target;
  554. if (!target) continue; // 如果没有目标元素,跳过
  555. if (processedElements.has(target)) continue; // 如果目标元素已经处理过,跳过
  556. if (this.isWithinThresholdDel(target, currentConnection.source, height)) continue;
  557. processedElements.add(target);
  558. // 遍历目标元素的所有出线连接,并将它们压入栈
  559. const outgoingConnections: any = target.outgoing || [];
  560. for (const outgoingConnection of outgoingConnections) {
  561. stack.push(outgoingConnection); // 将所有关联的连接线压入栈中
  562. }
  563. }
  564. return Array.from(processedElements);
  565. }
  566. public getElementsByGateway(gateway: any) {
  567. const elementsMap = new Map();
  568. let allElements = this._allElement;
  569. function getList(list: any) {
  570. list.map((element: any) => {
  571. if (element.id === gateway.id + '_confluence') return;
  572. if (!elementsMap.has(element.id)) {
  573. elementsMap.set(element.id, element);
  574. let childrenList = NodeUtils.getNextElementList(element, allElements);
  575. getList(childrenList);
  576. }
  577. return;
  578. });
  579. }
  580. let list = NodeUtils.getNextElementList(gateway, allElements);
  581. getList(list);
  582. return Array.from(elementsMap.values());
  583. }
  584. public resizeGroupShape(shapes: any[], bpmn: any) {
  585. let elementRegistry: any = bpmn.get('elementRegistry');
  586. let modeling: any = bpmn.get('modeling');
  587. let groupSet = new Set();
  588. shapes.length > 0 &&
  589. shapes.map((shape: any) => {
  590. // 1. 循环获取到移动元素判断是否为触发节点或执行节点 记录对应的分组id
  591. if (changeTypeByTaskShape[shape.wnType] || shape.wnType === typeTrigger) {
  592. groupSet.add(shape.businessObject.$attrs.customGroupId);
  593. }
  594. });
  595. for (let groupId of groupSet) {
  596. let minX = Infinity,
  597. minY = Infinity,
  598. maxX = -Infinity,
  599. maxY = -Infinity;
  600. let groupShape = elementRegistry.get(groupId);
  601. if (groupShape) {
  602. // 2. 遍历分组id的所有元素 获取到分组id内的边界坐标 根据坐标计算长宽高
  603. bpmn.get('elementRegistry').forEach(element => {
  604. if (element.businessObject.$attrs.customGroupId === groupId) {
  605. minX = Math.min(minX, element.x);
  606. minY = Math.min(minY, element.y);
  607. maxX = Math.max(maxX, element.x + element.width);
  608. maxY = Math.max(maxY, element.y + element.height);
  609. }
  610. });
  611. // 3. 根据触发节点位置 重新设置分组元素的坐标
  612. modeling.resizeShape(groupShape, {
  613. x: minX - 25,
  614. y: minY - 15,
  615. width: maxX - minX + 50,
  616. height: maxY - minY + 30,
  617. });
  618. }
  619. }
  620. }
  621. public getGroupElementById(groupId: string, bpmn: any) {
  622. let groupList: any = [];
  623. let groupShape = bpmn.get('elementRegistry').get(groupId);
  624. if (groupShape) {
  625. // 2. 遍历分组id的所有元素 获取到分组id内的边界坐标 根据坐标计算长宽高
  626. bpmn.get('elementRegistry').forEach(element => {
  627. if (element.businessObject.$attrs?.customGroupId === groupId || element.id === groupId) groupList.push(element);
  628. });
  629. }
  630. return groupList;
  631. }
  632. public getOutgoingConnections(element) {
  633. return element.outgoing || [];
  634. }
  635. public findUniqueElementsBetween(currentElement, targetElement, visitedElements = new Set()) {
  636. // 添加当前元素到访问路径集合
  637. visitedElements.add(currentElement);
  638. // 如果当前元素是目标元素,返回集合
  639. if (currentElement === targetElement) {
  640. return visitedElements;
  641. }
  642. const outgoingConnections = this.getOutgoingConnections(currentElement);
  643. outgoingConnections.forEach(connection => {
  644. const nextElement = connection.target;
  645. if (!visitedElements.has(nextElement)) {
  646. this.findUniqueElementsBetween(nextElement, targetElement, visitedElements);
  647. }
  648. });
  649. return visitedElements;
  650. }
  651. public onComputerMaxElementH(bpmn, current, gatewayElement, groupList, delElement, isGateway, processedElements?, threshold?) {
  652. // 获取对应的分流节点 并且获取到分流节点到合流内的所有元素
  653. let elementRegistry: any = bpmn.get('elementRegistry');
  654. let confluenceElement = elementRegistry.get(current.id);
  655. let treeBuilder = new BPMNTreeBuilder(elementRegistry.getAll()); // 实例化工具类
  656. let list: any = [];
  657. let groupH: number = 0;
  658. const uniqueElementsSet = treeBuilder.findUniqueElementsBetween(gatewayElement, confluenceElement);
  659. // 查找具有最大属性值的元素
  660. let maxElement: any = null;
  661. let maxValue = -Infinity; // 初始最大值设为负无穷大
  662. uniqueElementsSet.forEach((element: any) => {
  663. let y = element?.y; // 假设需要比较的属性名为 'value'
  664. if (processedElements?.has(element.id)) y += threshold;
  665. // 比较属性值并更新最大值和对应的元素
  666. if(element?.wnType === typeConfluence && maxElement?.wnType != typeConfluence) y = y + 220
  667. if (y > maxValue && current.id != element.id) {
  668. maxValue = y;
  669. maxElement = element;
  670. }
  671. });
  672. if (maxValue <= current.y - 220) {
  673. list.push(current);
  674. const taskHeight = typeConfig?.[bpmnTask]?.renderer?.attr?.height ;
  675. groupH = current.y - (maxValue + DEFAULT_DISTANCE + taskHeight);
  676. } else if (maxElement.wnType === current.wnType && current.wnType === typeConfluence) {
  677. list.push(current);
  678. }
  679. if (groupList?.length) {
  680. if (current.y > maxValue) {
  681. list.push(current);
  682. }
  683. }
  684. return { list: list, h: groupH };
  685. }
  686. public handleCollisionsByLevel(list: any[], type: direction) {
  687. const MIN_SPACE_X = 120; // 最小间距
  688. const MIN_SPACE_Y = 30;
  689. // 根据 level 对元素进行分组
  690. const grouped = groupByLevel(list);
  691. // 对每个层级的元素进行碰撞检测和位置调整
  692. grouped.forEach(group => {
  693. collisionFun(group);
  694. });
  695. function collisionFun(elements) {
  696. let y = elements[elements.length - 1].offset ? elements[elements.length - 1].offset.y : elements[elements.length - 1].y;
  697. let x = elements[elements.length - 1].offset ? elements[elements.length - 1].offset.x : elements[elements.length - 1].x;
  698. // 遍历每一对元素进行碰撞检测
  699. for (let i = 0; i < elements.length; i++) {
  700. for (let j = i + 1; j < elements.length; j++) {
  701. const rect1 = elements[i];
  702. const rect2 = elements[j];
  703. // 如果发生碰撞
  704. if (isColliding(rect1, rect2, type)) {
  705. // 调整位置,将碰撞元素移动到最后
  706. if (type === 'horizontal') {
  707. y = y + rect2.height + MIN_SPACE_Y;
  708. rect2.offset.y = y;
  709. } else {
  710. x = x + rect2.width + MIN_SPACE_X;
  711. rect2.offset.x = x;
  712. }
  713. }
  714. }
  715. }
  716. }
  717. function groupByLevel(list) {
  718. list.sort((a, b) => a.level - b.level);
  719. const grouped: any = [];
  720. let currentLevel = null;
  721. let currentGroup: any = [];
  722. list.forEach(item => {
  723. if (item.level !== currentLevel) {
  724. if (currentGroup.length > 0) {
  725. grouped.push(currentGroup);
  726. }
  727. currentGroup = [item];
  728. currentLevel = item.level;
  729. } else {
  730. currentGroup.push(item);
  731. }
  732. });
  733. if (currentGroup.length > 0) {
  734. currentGroup.sort((a, b) => {
  735. if (type === 'horizontal') {
  736. let aY = a.offset ? a.offset.y : a.y;
  737. let bY = b.offset ? b.offset.y : b.y;
  738. return aY - bY;
  739. } else {
  740. let aX = a.offset ? a.offset.x : a.x;
  741. let bX = b.offset ? b.offset.x : b.x;
  742. return aX - bX;
  743. }
  744. });
  745. grouped.push(currentGroup);
  746. }
  747. return grouped;
  748. }
  749. // y坐标碰撞检测
  750. function isColliding(element1: any, element2: any, type: direction) {
  751. if (type === 'horizontal') {
  752. let element1Y = element1.offset ? element1.offset.y : element1.y;
  753. let element2Y = element2.offset ? element2.offset.y : element2.y;
  754. return element1Y < element2Y + element2.height + MIN_SPACE_Y && element1Y + element1.height + MIN_SPACE_Y > element2Y;
  755. }
  756. if (type === 'vertical') {
  757. let element1X = element1.offset ? element1.offset.x : element1.x;
  758. let element2X = element2.offset ? element2.offset.x : element2.x;
  759. return element1X < element2X + element2.width + MIN_SPACE_X && element1X + element1.width + MIN_SPACE_X > element2X;
  760. }
  761. }
  762. }
  763. }