@mx-sose-front/mx-sose-graph 1.1.8 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/assets/edgeWorker-b57ca007.js +2 -0
  2. package/dist/assets/edgeWorker-b57ca007.js.map +1 -0
  3. package/dist/index.d.ts +633 -30
  4. package/dist/index.esm.js +8728 -4734
  5. package/dist/index.esm.js.map +1 -1
  6. package/dist/index.umd.js +1 -1
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/style.css +1 -1
  9. package/package.json +1 -1
  10. package/src/components/Common/Tree.vue +451 -0
  11. package/src/components/Common/index.ts +2 -0
  12. package/src/components/DiagramListTooltip/DiagramListTooltip.vue +1 -2
  13. package/src/components/Edge/Edge.vue +172 -169
  14. package/src/components/Gantt/Gantt.vue +1544 -0
  15. package/src/components/GanttContextMenu/GanttContextMenu.vue +304 -0
  16. package/src/components/InteractionLayer.vue +343 -147
  17. package/src/components/Matrix/Matrix.vue +828 -0
  18. package/src/components/Matrix/index.ts +168 -0
  19. package/src/components/Shape/ConceptualRole.vue +2 -34
  20. package/src/components/Table/Table.vue +970 -0
  21. package/src/constants/edgeShapeKeys.ts +8 -5
  22. package/src/constants/index.ts +259 -45
  23. package/src/hooks/index.ts +2 -0
  24. package/src/hooks/useChartRowSelection.ts +456 -0
  25. package/src/hooks/useResize.ts +2 -2
  26. package/src/hooks/useVirtualScroll.ts +258 -0
  27. package/src/index.ts +1 -1
  28. package/src/render/shape-renderer.ts +62 -2
  29. package/src/statics/icons/childIcons//345/221/275/344/273/244@3x.png +0 -0
  30. package/src/statics/icons/childIcons//346/210/230/347/225/245/346/246/202/345/277/265/350/241/250@3x.png +0 -0
  31. package/src/statics/icons/childIcons//346/216/247/345/210/266@3x.png +0 -0
  32. package/src/statics/icons/createMenu/down.png +0 -0
  33. package/src/statics/icons/createMenu/remove.png +0 -0
  34. package/src/statics/icons/createMenu/up.png +0 -0
  35. package/src/store/graphStore.ts +217 -44
  36. package/src/types/index.ts +86 -4
  37. package/src/utils/batchAutoExpand.ts +9 -10
  38. package/src/utils/containers.ts +72 -17
  39. package/src/utils/contextMenuUtils.ts +7 -7
  40. package/src/utils/dateUtils.ts +160 -0
  41. package/src/utils/diagram.ts +10 -8
  42. package/src/utils/drag.ts +6 -5
  43. package/src/utils/edgeUtils.ts +344 -427
  44. package/src/utils/edgeWorker.ts +471 -0
  45. package/src/utils/hittest.ts +37 -38
  46. package/src/utils/index.ts +3 -0
  47. package/src/utils/keyboardUtils.ts +5 -5
  48. package/src/utils/packageOutline.ts +96 -0
  49. package/src/utils/rafThrottle.ts +162 -0
  50. package/src/utils/workerManager.ts +335 -0
  51. package/src/view/graph.vue +47 -33
  52. /package/src/statics/icons/childIcons//346/210/230/347/225/{245@3x.png" → 245/345/261/202@3x.png"} +0 -0
package/src/index.ts CHANGED
@@ -14,7 +14,7 @@ export { useGraphStore, eventBus } from './store'
14
14
  export { ContextMenuUtils } from './utils/contextMenuUtils'
15
15
 
16
16
  // 导出类型
17
- export type { Shape, Bounds, Style, NameObject, TaggedValueLabel, Comparent, GraphEvents } from './types'
17
+ export type { Shape, Bounds, Style, NameObject, TaggedValueLabel, Comparent, GraphEvents, GanttData, GanttColumn, GanttContainsModel } from './types'
18
18
  /**
19
19
  * 可选的安装参数:允许使用方主动把 pinia 传进来
20
20
  */
@@ -1,6 +1,6 @@
1
1
  import type { CSSProperties } from "vue";
2
2
  import type { Shape } from "@/types";
3
- import { ShapeKeyMap, DiagramKeyMap, PinKeyMap, EdgeKeyMap } from "../constants";
3
+ import { ShapeKeyMap, DiagramKeyMap, PinKeyMap, EdgeKeyMap, GanttKeyMap, TableKeyMap, MatrixKeyMap } from "../constants";
4
4
  import { getShapeComponentByKey } from "./shape-registry";
5
5
 
6
6
  // 根据 shapeKey 匹配具体组件 (shapeType: 'shape')
@@ -21,6 +21,24 @@ function getComponentByEdgeKey(shapeKey: string) {
21
21
  return getShapeComponentByKey(componentName) || componentName;
22
22
  }
23
23
 
24
+ // 根据 shapeKey 匹配 Gantt 组件 (shapeType: 'gantt')
25
+ export function getComponentByGanttKey(shapeKey: string) {
26
+ const componentName = (GanttKeyMap as any)[shapeKey] || shapeKey;
27
+ return getShapeComponentByKey(componentName) || componentName;
28
+ }
29
+
30
+ // 根据 shapeKey 匹配 Table 组件 (shapeType: 'table')
31
+ export function getComponentByTableKey(shapeKey: string) {
32
+ const componentName = (TableKeyMap as any)[shapeKey] || shapeKey;
33
+ return getShapeComponentByKey(componentName) || componentName;
34
+ }
35
+
36
+ // 根据 shapeKey 匹配 Matrix 组件 (shapeType: 'matrix')
37
+ export function getComponentByMatrixKey(shapeKey: string) {
38
+ const componentName = (MatrixKeyMap as any)[shapeKey] || shapeKey;
39
+ return getShapeComponentByKey(componentName) || componentName;
40
+ }
41
+
24
42
  // 根据 shapeKey 匹配 Edge 组件 (shapeType: 'diagram')
25
43
  export function getComponentByDiagramKey(shapeKey: string) {
26
44
  const componentName = (DiagramKeyMap as any)[shapeKey] || shapeKey;
@@ -28,6 +46,7 @@ export function getComponentByDiagramKey(shapeKey: string) {
28
46
  }
29
47
 
30
48
  // 根据 shape 读取组件
49
+ // 注意:后端可能对矩阵/表格/甘特图也传 shapeType='diagram',需按 shapeKey 优先匹配 Matrix/Table/Gantt
31
50
  export const getShapeComponent = (shape: Shape) => {
32
51
  const { shapeType, shapeKey } = shape;
33
52
  switch (shapeType) {
@@ -36,9 +55,18 @@ export const getShapeComponent = (shape: Shape) => {
36
55
  case "shape":
37
56
  return getComponentByShapeKey(shapeKey);
38
57
  case "diagram":
58
+ if ((MatrixKeyMap as any)[shapeKey]) return getComponentByMatrixKey(shapeKey);
59
+ if ((TableKeyMap as any)[shapeKey]) return getComponentByTableKey(shapeKey);
60
+ if ((GanttKeyMap as any)[shapeKey]) return getComponentByGanttKey(shapeKey);
39
61
  return getComponentByDiagramKey(shapeKey);
40
62
  case "edge":
41
63
  return getComponentByEdgeKey(shapeKey);
64
+ case "gantt":
65
+ return getComponentByGanttKey(shapeKey);
66
+ case "table":
67
+ return getComponentByTableKey(shapeKey);
68
+ case "matrix":
69
+ return getComponentByMatrixKey(shapeKey);
42
70
  default:
43
71
  return "ShapeComponent";
44
72
  }
@@ -108,11 +136,43 @@ export function clearEdgeStyleCache(): void {
108
136
  }
109
137
 
110
138
  // 根据 shape 的 bounds 生成样式
139
+ // shapeType 为 diagram 但 shapeKey 对应 Matrix/Table/Gantt 时也使用铺满样式
111
140
  export const getShapeStyle = (shape: Shape): CSSProperties => {
112
- const { bounds, shapeType } = shape
141
+ const { bounds, shapeType, shapeKey } = shape
113
142
 
114
143
  if (shapeType === "edge") return getEdgeShapeStyle(shape)
115
144
 
145
+ const isDiagramAsMatrixOrTableOrGantt =
146
+ shapeType === "diagram" &&
147
+ ((MatrixKeyMap as any)[shapeKey] || (TableKeyMap as any)[shapeKey] || (GanttKeyMap as any)[shapeKey])
148
+ if (isDiagramAsMatrixOrTableOrGantt) {
149
+ return {
150
+ position: "relative",
151
+ width: "100%",
152
+ height: "100%",
153
+ }
154
+ }
155
+ if (shapeType === "gantt") {
156
+ return {
157
+ position: "relative",
158
+ width: "100%",
159
+ height: "100%",
160
+ }
161
+ }
162
+ if (shapeType === "table") {
163
+ return {
164
+ position: "relative",
165
+ width: "100%",
166
+ height: "100%",
167
+ }
168
+ }
169
+ if (shapeType === "matrix") {
170
+ return {
171
+ position: "relative",
172
+ width: "100%",
173
+ height: "100%",
174
+ }
175
+ }
116
176
  return {
117
177
  position: "absolute",
118
178
  left: `${bounds?.x || 0}px`,
@@ -20,13 +20,57 @@ import {
20
20
  syncShowComparentsByReparent,
21
21
  } from '../utils/compartment'
22
22
  import { EdgeUtils } from '../utils/edgeUtils'
23
+ import { snapPinToParentEdge } from '../utils/pinUtils'
23
24
  import { expandParentByChild } from '../utils/autoExpandParent'
24
25
  import { batchAutoExpandParents } from '../utils/batchAutoExpand'
25
26
  import { adjustCanvasToFitAllShapes } from '../utils/diagram'
26
27
  import { applyReparentAndClone, autoExpandMovedCompartmentsAfterDrag, buildDragEndPayloads, buildPrevParentMap, collectAffectedShapeIds, createOnNestDoneCallback } from '../utils/graphDragService'
27
28
  import { createShapeOperator, type ShapeOp, type ShapeId } from "../utils/shapeOps/shapeOps"
28
29
  type Rect = { x: number; y: number; width: number; height: number };
30
+
31
+ const GRAPH_SELECTION_STORAGE_KEY = 'mx-sose-graph:selected-state'
32
+
33
+ interface PersistedSelectionState {
34
+ diagramId: string | null
35
+ selectedIds: string[]
36
+ }
37
+
38
+ function loadPersistedSelectionState(): PersistedSelectionState {
39
+ if (typeof window === 'undefined') {
40
+ return { diagramId: null, selectedIds: [] }
41
+ }
42
+
43
+ try {
44
+ const raw = window.sessionStorage.getItem(GRAPH_SELECTION_STORAGE_KEY)
45
+ if (!raw) {
46
+ return { diagramId: null, selectedIds: [] }
47
+ }
48
+
49
+ const parsed = JSON.parse(raw) as Partial<PersistedSelectionState>
50
+ return {
51
+ diagramId: typeof parsed.diagramId === 'string' ? parsed.diagramId : null,
52
+ selectedIds: Array.isArray(parsed.selectedIds)
53
+ ? parsed.selectedIds.filter((id): id is string => typeof id === 'string')
54
+ : [],
55
+ }
56
+ } catch {
57
+ return { diagramId: null, selectedIds: [] }
58
+ }
59
+ }
60
+
61
+ function savePersistedSelectionState(state: PersistedSelectionState) {
62
+ if (typeof window === 'undefined') return
63
+
64
+ window.sessionStorage.setItem(GRAPH_SELECTION_STORAGE_KEY, JSON.stringify(state))
65
+ }
66
+
67
+ function clearPersistedSelectionState() {
68
+ if (typeof window === 'undefined') return
69
+
70
+ window.sessionStorage.removeItem(GRAPH_SELECTION_STORAGE_KEY)
71
+ }
29
72
  export const useGraphStore = defineStore('graph', () => {
73
+ const persistedSelectionState = loadPersistedSelectionState()
30
74
  // 状态
31
75
  const shapes = ref<Shape[]>([])
32
76
  // 在 store 初始化时创建一次
@@ -67,7 +111,8 @@ export const useGraphStore = defineStore('graph', () => {
67
111
  //当前激活的画布id
68
112
  const activeDiagramId = ref<string | null>(null);
69
113
  // 当前被选中的多个 id
70
- const selectedIds = ref<string[]>([])
114
+ const selectedIds = ref<string[]>(persistedSelectionState.selectedIds)
115
+ const selectionDiagramId = ref<string | null>(persistedSelectionState.diagramId)
71
116
  // 外部拖拽创建中的形状 id(用于隐藏实际形状,只显示 ghost)
72
117
  const externalCreatingId = ref<string | null>(null)
73
118
  // 剪切状态的图元 ID 集合(用于 SVG 遮盖层渲染)
@@ -112,6 +157,29 @@ export const useGraphStore = defineStore('graph', () => {
112
157
  return map;
113
158
  })
114
159
 
160
+ /**
161
+ * 连线索引映射
162
+ * 用于快速查找某个节点关联的所有连线
163
+ * key: 节点 ID (sourceId 或 targetId), value: 关联的 edge 数组
164
+ * 性能优化: 避免 updateRelatedEdges / initializeAllEdgeEndpoints 中的全量 filter + find
165
+ */
166
+ const edgesByNodeId = computed(() => {
167
+ const map = new Map<string, Shape[]>()
168
+ shapes.value.forEach(s => {
169
+ if (s.shapeType === 'edge') {
170
+ if (s.sourceId) {
171
+ if (!map.has(s.sourceId)) map.set(s.sourceId, [])
172
+ map.get(s.sourceId)!.push(s)
173
+ }
174
+ if (s.targetId) {
175
+ if (!map.has(s.targetId)) map.set(s.targetId, [])
176
+ map.get(s.targetId)!.push(s)
177
+ }
178
+ }
179
+ })
180
+ return map
181
+ })
182
+
115
183
  //图元shapeKey
116
184
  const taggedValueLabels = ref<string[]>([]) // 隔间组件的图元类型
117
185
  const packagesTypes = ref<string[]>([]) // 需要展示线的隔间组件的图元类型
@@ -150,6 +218,9 @@ export const useGraphStore = defineStore('graph', () => {
150
218
  // 通过事件总线发送事件
151
219
  // eventBus.emit('shape-added', shape)
152
220
 
221
+ // 图元新增后调整画布大小
222
+ nextTick(adjustCanvasToFitAllShapes)
223
+
153
224
  // 自动扩父逻辑:如果图元有父元素且需要自动扩父,触发扩父逻辑
154
225
  const shouldAutoExpand = options?.autoExpandParent !== false // 默认为 true
155
226
  if (shouldAutoExpand && shape.parenShapeId && shape.bounds) {
@@ -163,9 +234,19 @@ export const useGraphStore = defineStore('graph', () => {
163
234
  const removeShape = (shapeId: string) => {
164
235
  const index = shapes.value.findIndex(s => s.id == shapeId)
165
236
  if (index > -1) {
237
+ const wasPrimarySelected = selectedShape.value?.id === shapeId
238
+ shapes.value.splice(index, 1)
239
+ diagrams.value = diagrams.value.filter(diagram => diagram.id !== shapeId)
240
+ selectedIds.value = selectedIds.value.filter(id => id !== shapeId)
241
+ pendingNestedIds.value = pendingNestedIds.value.filter(id => id !== shapeId)
242
+ if (cutShapeIds.value.has(shapeId)) {
243
+ const nextCutShapeIds = new Set(cutShapeIds.value)
244
+ nextCutShapeIds.delete(shapeId)
245
+ cutShapeIds.value = nextCutShapeIds
246
+ }
166
247
  // 如果删除的是当前选中的形状,清除选中状态
167
- if (selectedShape.value?.id === shapeId) {
168
- selectedShape.value = null
248
+ syncSelectedState(wasPrimarySelected)
249
+ if (wasPrimarySelected && !selectedShape.value) {
169
250
  eventBus.emit('shape-deselected')
170
251
  }
171
252
  }
@@ -195,7 +276,9 @@ export const useGraphStore = defineStore('graph', () => {
195
276
  })
196
277
  // 更新单个图元(不处理父子关系 / comparents,同步由外层 updateShape 负责)
197
278
  const updateShapeRaw = (shapeId: string, updates: Partial<Shape>, id: 'id' | 'modelId' = 'id') => {
198
- const shape = shapes.value.find(s => s[id] === shapeId)
279
+ const shape = id === 'id'
280
+ ? shapeMap.value.get(shapeId) ?? null
281
+ : shapes.value.find(s => s[id] === shapeId)
199
282
  if (shape) {
200
283
  // 对 Pin 做强保护:永不允许把 parenShapeId 置空/undefined
201
284
  if (shape.shapeType === 'pin' && 'parenShapeId' in updates) {
@@ -218,18 +301,23 @@ export const useGraphStore = defineStore('graph', () => {
218
301
  }
219
302
  // 更新图元数据
220
303
  const updateShape = (shapeId: string, updates: Partial<Shape>, id: 'id' | 'modelId' = 'id') => {
221
- const before = shapes.value.find(s => s[id] === shapeId) || null
304
+ const before = id === 'id'
305
+ ? shapeMap.value.get(shapeId) ?? null
306
+ : shapes.value.find(s => s[id] === shapeId) ?? null
222
307
  const oldPid = before?.parenShapeId ?? null
223
308
  // 先真正写入
224
309
  updateShapeRaw(shapeId, updates, id)
225
- const after = shapes.value.find(s => s[id] === shapeId) || null
310
+ // updateShapeRaw 是就地修改,before after 是同一个引用(id 未变时)
311
+ const after = before
226
312
  if (!after) return
227
313
 
228
314
  // 如果更新了bounds,需要重新计算相关连线的位置
315
+ // 在高频场景(如拖拽)中使用同步版本保证即时性,
316
+ // 异步版本留给 endDragShape / endResizeShape 等"结束"时刻
229
317
  if (updates.bounds) {
230
318
  EdgeUtils.updateRelatedEdges(shapes.value, [after.id], (shape) => {
231
319
  updateShape(shape.id, shape);
232
- });
320
+ }, edgesByNodeId.value, shapeMap.value);
233
321
  }
234
322
 
235
323
  const newPid = after.parenShapeId ?? null
@@ -259,23 +347,71 @@ export const useGraphStore = defineStore('graph', () => {
259
347
  }
260
348
  const sameId = (a?: string | null, b?: string | null) =>
261
349
  (a ?? null) === (b ?? null)
350
+ const getLoadedDiagramId = () => shapes.value[0]?.diagramId ?? null
351
+ const persistSelectionState = () => {
352
+ if (!selectedIds.value.length) {
353
+ selectionDiagramId.value = null
354
+ clearPersistedSelectionState()
355
+ return
356
+ }
357
+
358
+ const selected = selectedIds.value
359
+ .map(id => shapeMap.value.get(id))
360
+ .find((shape): shape is Shape => !!shape)
361
+
362
+ selectionDiagramId.value = selected?.diagramId ?? selectionDiagramId.value ?? getLoadedDiagramId()
363
+ savePersistedSelectionState({
364
+ diagramId: selectionDiagramId.value,
365
+ selectedIds: [...selectedIds.value],
366
+ })
367
+ }
368
+ const syncSelectedState = (emit = false) => {
369
+ const loadedDiagramId = getLoadedDiagramId()
370
+ const ids = selectionDiagramId.value && loadedDiagramId && selectionDiagramId.value !== loadedDiagramId
371
+ ? []
372
+ : selectedIds.value.filter(id => shapeMap.value.has(id))
373
+ const nextSelectedShape = ids.length
374
+ ? (shapeMap.value.get(ids[0]) ?? null)
375
+ : null
376
+
377
+ selectedIds.value = ids
378
+ selectedShape.value = nextSelectedShape
379
+
380
+ if (nextSelectedShape) {
381
+ selectionDiagramId.value = nextSelectedShape.diagramId ?? loadedDiagramId
382
+ persistSelectionState()
383
+ } else {
384
+ selectionDiagramId.value = null
385
+ clearPersistedSelectionState()
386
+ }
387
+
388
+ if (emit) {
389
+ eventBus.emit('shape-selected', nextSelectedShape)
390
+ }
391
+ }
262
392
  //选中图元
263
393
  const selectShape = (shape: Shape | null) => {
264
394
  const prevId = selectedShape.value?.id ?? null
265
395
  const nextId = shape?.id ?? null
266
- if (sameId(prevId, nextId)) return
396
+ const nextSelectedIds = shape ? [shape.id] : []
397
+ const hasSameSelection =
398
+ selectedIds.value.length === nextSelectedIds.length &&
399
+ selectedIds.value.every((id, index) => id === nextSelectedIds[index])
400
+ if (sameId(prevId, nextId) && hasSameSelection) return
267
401
  selectedShape.value = shape
268
- selectedIds.value = shape ? [shape.id] : []
402
+ selectedIds.value = nextSelectedIds
403
+ selectionDiagramId.value = shape?.diagramId ?? null
404
+ persistSelectionState()
269
405
  // 通过事件总线发送事件
270
406
  eventBus.emit('shape-selected', shape)
271
407
  }
272
408
  // 被框选中的多个 shape/映射 selectedIds.value中的数据
273
409
  const marqueeShapes = computed(() => {
274
- const byId = (id: string) => shapes.value.find(s => s.id === id)
410
+ const map = shapeMap.value
275
411
  const isShape = (x: Shape | undefined): x is Shape => !!x
276
412
  const pending = new Set(pendingNestedIds.value)
277
413
  return selectedIds.value
278
- .map(byId)
414
+ .map(id => map.get(id))
279
415
  .filter(isShape)
280
416
  .filter(s => !pending.has(s.id))
281
417
  })
@@ -304,18 +440,24 @@ export const useGraphStore = defineStore('graph', () => {
304
440
  })
305
441
  // 选择一组(用于框选/Shift 叠加)
306
442
  const selectMany = (ids: string[]) => {
307
- selectedIds.value = Array.from(new Set(ids))
308
- selectedShape.value = ids.length
309
- ? (shapes.value.find(s => s.id === ids[0]) ?? null)
443
+ const nextIds = Array.from(new Set(ids))
444
+ selectedIds.value = nextIds
445
+ selectedShape.value = nextIds.length
446
+ ? (shapeMap.value.get(nextIds[0]) ?? null)
310
447
  : null
448
+ selectionDiagramId.value = selectedShape.value?.diagramId ?? getLoadedDiagramId()
449
+ persistSelectionState()
311
450
  }
312
451
  // 清空选择
313
452
  const clearSelection = () => selectShape(null)
314
453
  // 全选图元
315
454
  const selectAll = () => {
316
- // 获取所有非diagram类型的图元
455
+ // 获取所有非diagram、非edge类型的图元
317
456
  const allShapeIds = shapes.value
318
- .filter(shape => shape.shapeType?.toLowerCase() !== 'diagram')
457
+ .filter(shape => {
458
+ const t = shape.shapeType?.toLowerCase()
459
+ return t !== 'diagram' && t !== 'edge'
460
+ })
319
461
  .map(shape => shape.id)
320
462
  selectMany(allShapeIds)
321
463
  }
@@ -346,6 +488,7 @@ export const useGraphStore = defineStore('graph', () => {
346
488
  op,
347
489
  })
348
490
  pendingNestedIds.value = []
491
+ syncSelectedState(op === 'replace' || op === 'delete')
349
492
  // eventBus.emit('shapes-updated', newShapes)
350
493
  // hydrateAllComparents()
351
494
 
@@ -356,6 +499,9 @@ export const useGraphStore = defineStore('graph', () => {
356
499
  batchAutoExpandParents(changedShapes, updateShape)
357
500
  })
358
501
  }
502
+
503
+ // 图元增删后调整画布大小(用 nextTick 合并同一 tick 内的多次调用)
504
+ nextTick(adjustCanvasToFitAllShapes)
359
505
  }
360
506
  // 获取当前图元数据
361
507
  const getShapes = () => {
@@ -366,12 +512,15 @@ export const useGraphStore = defineStore('graph', () => {
366
512
  shapes.value = []
367
513
  diagrams.value = []
368
514
  selectedShape.value = null
515
+ selectedIds.value = []
516
+ selectionDiagramId.value = null
369
517
  pendingNestedIds.value = []
518
+ clearPersistedSelectionState()
370
519
  eventBus.emit('shapes-cleared')
371
520
  }
372
521
  //拖动元素相关操作
373
522
  // 开始拖动已有元素(仅对 shape 生效)
374
- const byId = (id: string) => shapes.value.find(s => s.id === id) || null
523
+ const byId = (id: string) => shapeMap.value.get(id) ?? null
375
524
  const startDragShape = (shapeId: string, pointer: { x: number; y: number }) => {
376
525
  startDrag([shapeId], pointer)
377
526
  }
@@ -621,7 +770,7 @@ export const useGraphStore = defineStore('graph', () => {
621
770
  }
622
771
  // —— Pin 终极兜底:不允许在本次拖拽后变成“无父” ——
623
772
  for (const id of changedIds) {
624
- const node = shapes.value.find(s => s.id === id)
773
+ const node = shapeMap.value.get(id) ?? null
625
774
  if (node?.shapeType === 'pin') {
626
775
  const prevPid = prevParentById[id]
627
776
  const nowPid = node.parenShapeId ?? null
@@ -632,9 +781,9 @@ export const useGraphStore = defineStore('graph', () => {
632
781
  }
633
782
 
634
783
  // 更新相关 edge 的 waypoints
635
- EdgeUtils.updateRelatedEdges(shapes.value, changedIds, (shape) => {
784
+ await EdgeUtils.updateRelatedEdgesAsync(shapes.value, changedIds, (shape) => {
636
785
  updateShape(shape.id, shape)
637
- })
786
+ }, edgesByNodeId.value, shapeMap.value)
638
787
  //对“本次直接拖动的隔间”做一次自动扩容检查
639
788
  autoExpandMovedCompartmentsAfterDrag(shapes.value, changedIds, updateShape)
640
789
  // 把 clone 也并入“需要同步 comparents 的变更集合”
@@ -655,7 +804,7 @@ export const useGraphStore = defineStore('graph', () => {
655
804
  // 在生成 payloads 之前,把父扩容先做掉
656
805
  const shapeId = ownerPayload?.shapeId
657
806
  if (shapeId != null) {
658
- const child = shapes.value.find(s => s.id == shapeId)
807
+ const child = shapeMap.value.get(shapeId) ?? null
659
808
  if (child) {
660
809
  expandParentByChild({
661
810
  shapes: shapes.value,
@@ -696,16 +845,45 @@ export const useGraphStore = defineStore('graph', () => {
696
845
  cleanupAfterDrag()
697
846
  }
698
847
  // 处理缩放结束后的事件发射
699
- const endResizeShape = (id: string) => {
848
+ const endResizeShape = async (id: string) => {
700
849
  // 更新相关edge的waypoints
701
- EdgeUtils.updateRelatedEdges(shapes.value, [id], (shape) => {
850
+ await EdgeUtils.updateRelatedEdgesAsync(shapes.value, [id], (shape) => {
702
851
  updateShape(shape.id, shape);
703
- });
852
+ }, edgesByNodeId.value, shapeMap.value);
853
+
854
+ // 重新计算子栓/端口的位置(父图元缩放后,栓需要贴合新的边缘)
855
+ const parentShape = shapeMap.value.get(id);
856
+ if (parentShape) {
857
+ const childIds = parentChildMap.value.get(id) ?? [];
858
+ for (const childId of childIds) {
859
+ const childShape = shapeMap.value.get(childId);
860
+ if (!childShape) continue;
861
+ if (
862
+ pinsTypes.value.includes(childShape.shapeKey) ||
863
+ portsTypes.value.includes(childShape.shapeKey)
864
+ ) {
865
+ // 用栓的中心点作为参考点,重新吸附到父图元最近的边
866
+ const pb = childShape.bounds ?? {};
867
+ const pinCenterX = (pb.x ?? 0) + (pb.width ?? 0) / 2;
868
+ const pinCenterY = (pb.y ?? 0) + (pb.height ?? 0) / 2;
869
+ const newPos = snapPinToParentEdge(
870
+ { x: pinCenterX, y: pinCenterY },
871
+ parentShape,
872
+ childShape
873
+ );
874
+ updateShape(childId, {
875
+ bounds: { ...childShape.bounds, x: newPos.x, y: newPos.y },
876
+ });
877
+ }
878
+ }
879
+ }
704
880
 
705
881
  // 收集受影响的元素
706
882
  const affected = new Set<string>();
707
883
  affected.add(id);
708
- const parentId = shapes.value.find(s => s.id === id)?.parenShapeId ?? null;
884
+ // 将缩放图元的所有子孙(包括栓/端口)加入受影响集合
885
+ collectDescendantIds(shapes.value, id).forEach(cid => affected.add(cid));
886
+ const parentId = shapeMap.value.get(id)?.parenShapeId ?? null;
709
887
  if (parentId) {
710
888
  affected.add(parentId);
711
889
  collectDescendantIds(shapes.value, parentId).forEach(cid => affected.add(cid));
@@ -716,8 +894,8 @@ export const useGraphStore = defineStore('graph', () => {
716
894
  let cur: string | null = affectedId;
717
895
  let guard = 0;
718
896
  while (cur && guard++ < 1000) {
719
- const s = shapes.value.find(x => x.id === cur);
720
- const p = s ? (s as any).parenShapeId ?? null : null;
897
+ const s: Shape | null = shapeMap.value.get(cur!) ?? null;
898
+ const p: string | null = s ? (s as any).parenShapeId ?? null : null;
721
899
  if (!p) break;
722
900
  affected.add(p);
723
901
  cur = p;
@@ -726,9 +904,10 @@ export const useGraphStore = defineStore('graph', () => {
726
904
 
727
905
  // 组装并派发shape-drag-end事件
728
906
  const payloads = Array.from(affected)
729
- .map(affectedId => shapes.value.find(s => s.id === affectedId))
907
+ .map(affectedId => shapeMap.value.get(affectedId))
730
908
  .filter(Boolean)
731
909
  .map(s => _.cloneDeep(s!)) as Shape[];
910
+ adjustCanvasToFitAllShapes()
732
911
  eventBus.emit('shape-drag-end', payloads,);
733
912
  };
734
913
 
@@ -793,10 +972,10 @@ export const useGraphStore = defineStore('graph', () => {
793
972
  * 初始化所有连线的端点
794
973
  * 用于在初始加载后端数据后,自动计算所有连线的合适端点,避免线横跨图元
795
974
  */
796
- const initializeAllEdgeEndpoints = () => {
797
- EdgeUtils.initializeAllEdgeEndpoints(shapes.value, (shape) => {
975
+ const initializeAllEdgeEndpoints = async () => {
976
+ await EdgeUtils.initializeAllEdgeEndpointsAsync(shapes.value, (shape) => {
798
977
  updateShape(shape.id, shape);
799
- });
978
+ }, shapeMap.value);
800
979
  }
801
980
  /**
802
981
  * 由“子元素数据”触发的扩父入口:
@@ -806,12 +985,13 @@ export const useGraphStore = defineStore('graph', () => {
806
985
  */
807
986
  const collectAncestors = (shapes: Shape[], id: string) => {
808
987
  const out: string[] = []
809
- let cur = shapes.find(s => s.id === id)
988
+ const map = shapeMap.value
989
+ let cur = map.get(id)
810
990
  while (cur) {
811
991
  out.push(cur.id)
812
992
  const pid = cur.parenShapeId
813
993
  if (!pid) break
814
- cur = shapes.find(s => s.id === pid)
994
+ cur = map.get(pid)
815
995
  }
816
996
  return out
817
997
  }
@@ -827,7 +1007,7 @@ export const useGraphStore = defineStore('graph', () => {
827
1007
  const before = new Map(
828
1008
  chainIds.map(id => [
829
1009
  id,
830
- JSON.stringify(shapes.value.find(s => s.id === id)?.bounds ?? {}),
1010
+ JSON.stringify(shapeMap.value.get(id)?.bounds ?? {}),
831
1011
  ]),
832
1012
  )
833
1013
 
@@ -840,7 +1020,7 @@ export const useGraphStore = defineStore('graph', () => {
840
1020
 
841
1021
  const payload: ShapeSizeUpdatePayload[] = chainIds
842
1022
  .filter(id => id !== child.id)
843
- .map(id => shapes.value.find(s => s.id === id))
1023
+ .map(id => shapeMap.value.get(id))
844
1024
  .filter((s): s is Shape => !!s)
845
1025
  .filter(s => before.get(s.id) !== JSON.stringify(s.bounds ?? {})) // diff
846
1026
  .map(s => ({ id: s.id, bounds: JSON.stringify(s.bounds ?? {}) }))
@@ -905,6 +1085,7 @@ export const useGraphStore = defineStore('graph', () => {
905
1085
  hasSelectedShape,
906
1086
  parentChildMap, // 父子关系索引映射
907
1087
  shapeMap, // 图元ID索引映射 (性能优化)
1088
+ edgesByNodeId, // 连线索引映射 (性能优化)
908
1089
  marqueeShapes,
909
1090
  ghostShadow,
910
1091
  scales,
@@ -950,12 +1131,4 @@ export const useGraphStore = defineStore('graph', () => {
950
1131
  clearCutShapeIds,
951
1132
  setCopiedShapesCount,
952
1133
  }
953
- },
954
- //配置持久化
955
- {
956
- //@ts-ignore
957
- persist: {
958
- paths: ['shapes', 'selectedShape']
959
- } as any,
960
- }
961
- )
1134
+ })