@mx-sose-front/mx-sose-graph 1.1.2 → 1.1.4

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 (48) hide show
  1. package/dist/index.d.ts +178 -10
  2. package/dist/index.esm.js +4944 -63227
  3. package/dist/index.esm.js.map +1 -1
  4. package/dist/index.umd.js +1 -38
  5. package/dist/index.umd.js.map +1 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +10 -1
  8. package/src/components/ContextMenu/ContextMenu.vue +27 -13
  9. package/src/components/DiagramListTooltip/DiagramListTooltip.vue +7 -12
  10. package/src/components/InteractionLayer.vue +656 -496
  11. package/src/components/LineStyle/LineStyleMarker.vue +1 -1
  12. package/src/components/NameEditor/NameEditor.vue +212 -0
  13. package/src/components/SelectionBox/SelectionBox.vue +189 -0
  14. package/src/components/Shape/Block.vue +1 -1
  15. package/src/constants/edgeShapeKeys.ts +43 -3
  16. package/src/constants/index.ts +21 -4
  17. package/src/hooks/index.ts +3 -0
  18. package/src/hooks/useHighlight.ts +223 -0
  19. package/src/hooks/useNameEdit.ts +234 -0
  20. package/src/{utils/resizeUtils.ts → hooks/useResize.ts} +55 -155
  21. package/src/index.ts +4 -1
  22. package/src/render/shape-renderer.ts +59 -46
  23. package/src/statics/icons/createMenu/show.png +0 -0
  24. package/src/statics/icons/createMenu/tree.png +0 -0
  25. package/src/statics/icons/createMenu//345/261/225/347/244/272/347/253/257/345/217/243/345/261/236/346/200/247@3x.png +0 -0
  26. package/src/statics/icons/createMenu//345/261/225/347/244/272/350/277/236/347/272/277@3x.png +0 -0
  27. package/src/statics/icons/createMenu//346/211/200/345/234/250/345/233/276/350/241/250@3x.png +0 -0
  28. package/src/store/graphStore.ts +185 -65
  29. package/src/types/index.ts +4 -2
  30. package/src/types/interactionLayer.ts +1 -0
  31. package/src/utils/batchAutoExpand.ts +65 -0
  32. package/src/utils/compartment.ts +78 -4
  33. package/src/utils/containers.ts +24 -10
  34. package/src/utils/contextMenuUtils.ts +126 -110
  35. package/src/utils/diagram.ts +19 -15
  36. package/src/utils/drag.ts +10 -5
  37. package/src/utils/edgeUtils.ts +3 -4
  38. package/src/utils/graphDragService.ts +27 -23
  39. package/src/utils/iconLoader.ts +7 -7
  40. package/src/utils/keyboardUtils.ts +221 -30
  41. package/src/utils/pinUtils.ts +1 -2
  42. package/src/utils/shapeOps/shapeOps.ts +168 -0
  43. package/src/utils/viewportCulling.ts +193 -0
  44. package/src/view/graph.vue +115 -60
  45. package/src/utils/highlightUtils.ts +0 -162
  46. package/src/utils/nameEditUtils.ts +0 -132
  47. package/src/utils/packgeMap.ts +0 -1
  48. /package/src/statics/icons/createMenu/{scissors.png → cut.png} +0 -0
@@ -70,22 +70,26 @@ export const getContentRect = (parent: Shape) => {
70
70
 
71
71
  /** 判断 childId 是否是 ancestorId 的后代 */
72
72
  export const isDescendant = (shapes: Shape[], childId: string, ancestorId: string) => {
73
- let p = shapes.find(s => s.id === childId)?.parenShapeId ?? null
73
+ const graphStore = useGraphStore();
74
+ const byId = graphStore.shapeMap;
75
+
76
+ let p = byId.get(childId)?.parenShapeId ?? null
74
77
  while (p) {
75
78
  if (p === ancestorId) return true
76
- p = shapes.find(s => s.id === p)?.parenShapeId ?? null
79
+ p = byId.get(p)?.parenShapeId ?? null // ✅ O(1) 查找,替代 find()
77
80
  }
78
81
  return false
79
82
  }
80
83
 
81
- /** 收集整棵子树(仅 id */
84
+ /** 收集整棵子树(仅 id) */
82
85
  export const collectDescendantIds = (shapes: Shape[], parentId: string): string[] => {
86
+ const graphStore = useGraphStore();
83
87
  const out: string[] = []
84
88
  const stack = [parentId]
85
89
  while (stack.length) {
86
90
  const id = stack.pop()!
87
- const kids = shapes.filter(s => s.parenShapeId === id)
88
- kids.forEach(k => { out.push(k.id); stack.push(k.id) })
91
+ const childIds = graphStore.parentChildMap.get(id) || []
92
+ childIds.forEach(childId => { out.push(childId); stack.push(childId) })
89
93
  }
90
94
  return out
91
95
  }
@@ -156,9 +160,12 @@ export const ensureParentFitsChildren = (
156
160
  updateShape: Updater,
157
161
  overrideRects?: Record<string, Rect>
158
162
  ) => {
159
- const kids = shapes.filter(
160
- s => s.parenShapeId === parent.id && !isPinShape(s)
161
- )
163
+ const graphStore = useGraphStore();
164
+ const childIds = graphStore.parentChildMap.get(parent.id) || [];
165
+ const kids = childIds
166
+ .map(id => graphStore.shapeMap.get(id))
167
+ .filter(s => s && !isPinShape(s)) as Shape[];
168
+
162
169
  if (kids.length === 0) return
163
170
 
164
171
  const pad = readPadding(parent)
@@ -320,7 +327,7 @@ export const childrenUnionBounds = (
320
327
  parentId: string,
321
328
  overrideRects?: Record<string, Rect>
322
329
  ) => {
323
- const kids = shapes.filter(
330
+ const kids = shapes.filter(
324
331
  s => s.parenShapeId === parentId && !isPinShape(s)
325
332
  )
326
333
  if (!kids.length) return null
@@ -512,7 +519,7 @@ export const hasDraggedAncestor = (
512
519
  * - 组拖后代:禁止 reparent;只夹回/必要才扩父
513
520
  * - 返回:是否发生 reparent(可用于统计/调试)
514
521
  */
515
- const DETACH_THRESHOLD = 0.4 // 覆盖率低于该阈值就脱离,不再扩父
522
+ const DETACH_THRESHOLD = 0.01 // 覆盖率低于该阈值就脱离,不再扩父
516
523
  /** 哪些图元一旦挂上父,就不允许脱离父(拖动时只能让父扩容) */
517
524
  export const isStickyChild = (s: Shape): boolean => {
518
525
  if (!Object.prototype.hasOwnProperty.call(s, 'isMovableComparents')) {
@@ -547,6 +554,13 @@ export const resolveParentAfterDrag = (
547
554
  if (!curParent) { updateShape(s.id, { bounds: finalRect }); return false }
548
555
  // 子就地落位(不回弹/不clamp,避免跳动)
549
556
  updateShape(s.id, { bounds: finalRect })
557
+
558
+ // 关键修复:拖动父图元时,子 pin 只应该“跟随平移”,不应被当作普通子元素去做
559
+ // overflow/clamp/扩父(这些会把 pin 夹进内容区,常表现为被重算到父图元下方)。
560
+ if (isPinShape(s)) {
561
+ return false
562
+ }
563
+
550
564
  if (isCompartment(curParent)) {
551
565
  // 如果父是隔间,拖动时父移动了,需要刷新隔间内部布局
552
566
  ensureCompartmentAfterParentMoved(shapes, curParent, updateShape) // 新增调用
@@ -1,45 +1,15 @@
1
- import { ref, type Ref } from 'vue';
1
+ import { type Ref } from 'vue';
2
2
  import { eventBus } from '../store';
3
-
4
- // 菜单位置接口
5
- export interface MenuPosition {
6
- x: number;
7
- y: number;
8
- }
9
-
10
- // 菜单项配置接口
11
- export interface MenuItemConfig {
12
- id: string;
13
- label: string;
14
- icon?: string;
15
- disabled?: boolean;
16
- divider?: boolean;
17
- handler?: () => void;
18
- submenu?: MenuItemConfig[];
19
- }
20
-
21
- // 菜单配置接口
22
- export interface MenuConfig {
23
- items: MenuItemConfig[];
24
- width?: number;
25
- itemHeight?: number;
26
- padding?: number;
27
- maxVisibleItems?: number;
28
- safeMargin?: number;
29
- }
30
-
31
- // 默认菜单配置
32
- const DEFAULT_MENU_CONFIG: Required<MenuConfig> = {
33
- items: [],
34
- width: 180,
35
- itemHeight: 36,
36
- padding: 6,
37
- maxVisibleItems: 8,
38
- safeMargin: 10
39
- };
3
+ import { useGraphStore } from '../store/graphStore';
4
+ import _ from 'lodash';
40
5
 
41
6
  // 右键菜单工具函数
42
7
  export class ContextMenuUtils {
8
+ // 用于存储复制的图元
9
+ private static copiedShapes: any[] = [];
10
+ // 用于标记操作类型:'copy' 或 'cut'
11
+ private static operationType: 'copy' | 'cut' = 'copy';
12
+
43
13
  /**
44
14
  * 处理右键菜单点击事件
45
15
  * @param event 鼠标事件
@@ -58,7 +28,8 @@ export class ContextMenuUtils {
58
28
  shapes: any[],
59
29
  selectShape?: (shape: any) => void,
60
30
  isDragging?: boolean,
61
- isResizing?: boolean
31
+ isResizing?: boolean,
32
+ scale: number = 1
62
33
  ): any | null {
63
34
  // 如果图元正在移动或缩放,则不显示菜单
64
35
  if (isDragging || isResizing) {
@@ -67,13 +38,13 @@ export class ContextMenuUtils {
67
38
 
68
39
  event.preventDefault();
69
40
 
70
- // 获取点击位置
71
- const point = ContextMenuUtils.toLocalPoint(event, layerRef.value);
41
+ // 获取点击位置,考虑缩放比例
42
+ const point = ContextMenuUtils.toLocalPoint(event, layerRef.value, scale);
72
43
 
73
44
  // 使用命中测试找到点击的图元
74
45
  const hit = pickTarget(shapes, point);
75
46
 
76
- if (hit.kind === "shape" && hit.shape) {
47
+ if ((hit.kind === "shape" || hit.kind === "pin") && hit.shape) {
77
48
  // 选中图元
78
49
  if (selectShape) {
79
50
  selectShape(hit.shape);
@@ -86,16 +57,18 @@ export class ContextMenuUtils {
86
57
 
87
58
  /**
88
59
  * 将鼠标事件坐标转换为本地坐标
60
+ * 注意:需要考虑画布的缩放比例
89
61
  */
90
- static toLocalPoint(event: MouseEvent, layerRef: HTMLElement | null): { x: number; y: number } {
62
+ static toLocalPoint(event: MouseEvent, layerRef: HTMLElement | null, scale: number = 1): { x: number; y: number } {
91
63
  if (!layerRef) {
92
64
  return { x: event.clientX, y: event.clientY };
93
65
  }
94
66
 
95
67
  const rect = layerRef.getBoundingClientRect();
68
+ // 计算本地坐标并除以缩放比例
96
69
  return {
97
- x: event.clientX - rect.left,
98
- y: event.clientY - rect.top
70
+ x: (event.clientX - rect.left) / scale,
71
+ y: (event.clientY - rect.top) / scale
99
72
  };
100
73
  }
101
74
 
@@ -111,9 +84,9 @@ export class ContextMenuUtils {
111
84
  * 计算菜单位置(避免超出屏幕边界)
112
85
  */
113
86
  static calculateMenuPosition(
114
- x: number,
115
- y: number,
116
- menuWidth: number = 180,
87
+ x: number,
88
+ y: number,
89
+ menuWidth: number = 180,
117
90
  menuHeight: number = 300,
118
91
  safeMargin: number = 10
119
92
  ): { x: number; y: number } {
@@ -174,12 +147,77 @@ export class ContextMenuUtils {
174
147
  }
175
148
  }
176
149
 
150
+ /**
151
+ * 设置复制的图元
152
+ */
153
+ static setCopiedShapes(shapes: any[]) {
154
+ this.copiedShapes = _.cloneDeep(shapes);
155
+ console.log('已复制的图元:', this.copiedShapes);
156
+ }
157
+
158
+ /**
159
+ * 获取复制的图元
160
+ */
161
+ static getCopiedShapes(): any[] {
162
+ return this.copiedShapes;
163
+ }
164
+
165
+ /**
166
+ * 清除剪切状态
167
+ * 当按 ESC 取消或其他需要清除剪切状态的场景调用
168
+ */
169
+ static clearCutState() {
170
+ if (this.operationType === 'cut') {
171
+ const graphStore = useGraphStore();
172
+ graphStore.clearCutShapeIds();
173
+ }
174
+ this.copiedShapes = [];
175
+ this.operationType = 'copy';
176
+
177
+ // 清空 GraphStore 中的剪贴板数量
178
+ const graphStore = useGraphStore();
179
+ graphStore.setCopiedShapesCount(0);
180
+ }
181
+
182
+ /**
183
+ * 收集图元及其所有子图元的 ID
184
+ * @param shapes 要收集的图元数组
185
+ * @returns 所有图元 ID 的数组(包括子图元)
186
+ */
187
+ private static collectShapeAndChildrenIds(shapes: any[]): string[] {
188
+ const graphStore = useGraphStore();
189
+ const idsToCollect: string[] = [];
190
+
191
+ // 收集所有需要的图元 ID (包括父图元和子图元)
192
+ shapes.forEach(shape => {
193
+ idsToCollect.push(shape.id);
194
+
195
+ // 使用父子关系索引快速查找子图元 (O(1) 查询)
196
+ const childIds = graphStore.parentChildMap.get(shape.id) || [];
197
+ childIds.forEach(childId => {
198
+ idsToCollect.push(childId);
199
+ });
200
+ });
201
+
202
+ return idsToCollect;
203
+ }
204
+
177
205
  /**
178
206
  * 处理复制
179
207
  */
180
- static handleCopy(target: any) {
181
- if (target) {
182
- eventBus.emit('copy-shape', target);
208
+ static handleCopy(targets: any[]) {
209
+ if (targets && targets.length > 0) {
210
+ // 如果之前是剪切状态,先清除剪切遮盖层
211
+ if (this.operationType === 'cut') {
212
+ const graphStore = useGraphStore();
213
+ graphStore.clearCutShapeIds();
214
+ }
215
+ this.operationType = 'copy';
216
+ this.setCopiedShapes(targets);
217
+
218
+ // 更新 GraphStore 中的剪贴板数量
219
+ const graphStore = useGraphStore();
220
+ graphStore.setCopiedShapesCount(targets.length);
183
221
  }
184
222
  }
185
223
 
@@ -187,17 +225,49 @@ export class ContextMenuUtils {
187
225
  * 处理粘贴
188
226
  */
189
227
  static handlePaste(target: any) {
190
- if (target) {
191
- eventBus.emit('paste-shape', target);
228
+ if (this.copiedShapes.length === 0) {
229
+ return;
192
230
  }
231
+
232
+ let pastedShapes: string[] = this.copiedShapes.map(s => s.id);
233
+
234
+ // 通过事件总线通知添加图元,包含操作类型
235
+ eventBus.emit('paste-shapes', {
236
+ shapeIds: pastedShapes,
237
+ operationType: this.operationType
238
+ });
239
+
240
+ // 如果是剪切操作,粘贴后清除剪切状态遮盖层和剪贴板
241
+ if (this.operationType === 'cut') {
242
+ const graphStore = useGraphStore();
243
+ graphStore.clearCutShapeIds();
244
+
245
+ // 剪切粘贴后清空剪贴板数量
246
+ graphStore.setCopiedShapesCount(0);
247
+ this.copiedShapes = [];
248
+ this.operationType = 'copy';
249
+ }
250
+
251
+ console.log('已粘贴的图元:', pastedShapes, '操作类型:', this.operationType);
193
252
  }
194
253
 
195
254
  /**
196
255
  * 处理剪切
197
256
  */
198
- static handleCut(target: any) {
199
- if (target) {
200
- eventBus.emit('cut-shape', target);
257
+ static handleCut(targets: any[]) {
258
+ if (targets && targets.length > 0) {
259
+ this.operationType = 'cut';
260
+ this.setCopiedShapes(targets);
261
+
262
+ // 收集所有需要显示剪切遮盖层的图元 ID(包括子图元)
263
+ const idsToMark = this.collectShapeAndChildrenIds(targets);
264
+
265
+ // 使用 SVG 遮盖层方案:设置剪切状态的图元 ID
266
+ const graphStore = useGraphStore();
267
+ graphStore.setCutShapeIds(idsToMark);
268
+
269
+ // 更新 GraphStore 中的剪贴板数量
270
+ graphStore.setCopiedShapesCount(targets.length);
201
271
  }
202
272
  }
203
273
 
@@ -208,57 +278,3 @@ export class ContextMenuUtils {
208
278
  console.log('当前选中的图元:', target);
209
279
  }
210
280
  }
211
-
212
- // 默认菜单配置
213
- export const defaultMenuItems: MenuItemConfig[] = [
214
- {
215
- id: 'property',
216
- label: '属性配置',
217
- icon: 'config'
218
- },
219
- {
220
- id: 'highlight',
221
- label: '树上高亮',
222
- icon: 'delete'
223
- },
224
- {
225
- id: 'locate-chart',
226
- label: '所在图表',
227
- icon: 'locateChart',
228
- divider: true
229
- },
230
- {
231
- id: 'new-chart',
232
- label: '新建图表',
233
- icon: 'diagram',
234
- disabled: true
235
- },
236
- {
237
- id: 'copy',
238
- label: '复制',
239
- icon: 'copy',
240
- disabled: true
241
- },
242
- {
243
- id: 'paste',
244
- label: '粘贴',
245
- icon: 'paste',
246
- disabled: true
247
- },
248
- {
249
- id: 'cut',
250
- label: '剪切',
251
- icon: 'scissors',
252
- disabled: true
253
- },
254
- {
255
- id: 'delete',
256
- label: '删除',
257
- icon: 'delete'
258
- }
259
- ];
260
-
261
- // 标准右键菜单配置
262
- export const standardContextMenuConfig: MenuConfig = {
263
- items: defaultMenuItems
264
- };
@@ -375,13 +375,17 @@ export const nameInputStyle = (shape: Shape): Record<string, string> => {
375
375
  * 名称编辑容器样式 - 确保在父组件内水平居中
376
376
  */
377
377
  export const nameEditorContainerStyle = (shape: Shape): Record<string, string> => {
378
- const b = shape.bounds ?? {} as any
379
- const nb = (shape as any).nameBounds ?? {} as any
380
- const ns = (shape as any).nameStyle ?? {} as any
378
+ // 使用shape.id重新从store获取最新的图元信息,确保位置是最新的
379
+ const graphStore = useGraphStore();
380
+ const latestShape = graphStore.shapes.find(s => s.id === shape.id) || shape;
381
+
382
+ const b = latestShape.bounds ?? {} as any
383
+ const nb = (latestShape as any).nameBounds ?? {} as any
384
+ const ns = (latestShape as any).nameStyle ?? {} as any
381
385
 
382
- if ((shape as any).shapeType === 'pin' && (nb.x != null) && (nb.y != null)) {
386
+ if ((latestShape as any).shapeType === 'pin' && (nb.x != null) && (nb.y != null)) {
383
387
  const fontSize = Number(ns.fontSize || nb.height || 12)
384
- const textLen = (shape.name?.length || 0)
388
+ const textLen = (latestShape.name?.length || 0)
385
389
  const textWidth = Math.max(10, textLen * fontSize * 0.6)
386
390
  const boxW = Math.min(300, Math.max(40, textWidth + 14))
387
391
  const boxH = Math.max(22, fontSize + 10)
@@ -403,18 +407,18 @@ export const nameEditorContainerStyle = (shape: Shape): Record<string, string> =
403
407
  else top = yAbs - fontSize
404
408
 
405
409
  return {
406
- position: 'absolute',
407
- left: `${Math.round(left)}px`,
408
- top: `${Math.round(top)}px`,
409
- width: `${Math.round(boxW)}px`,
410
- height: `${Math.round(boxH)}px`,
411
- display: 'block',
412
- zIndex: '1001',
413
- }
410
+ position: 'absolute',
411
+ left: `${Math.round(left)}px`,
412
+ top: `${Math.round(top)}px`,
413
+ width: `${Math.round(boxW)}px`,
414
+ height: `${Math.round(boxH)}px`,
415
+ display: 'block',
416
+ zIndex: '1001',
414
417
  }
418
+ }
415
419
 
416
- const shapeBounds = shape.bounds ?? {};
417
- const nameBounds = shape.nameBounds ?? {};
420
+ const shapeBounds = latestShape.bounds ?? {};
421
+ const nameBounds = latestShape.nameBounds ?? {};
418
422
 
419
423
  // 计算text name在画布中的绝对位置
420
424
  const absoluteY = (shapeBounds.y ?? 0) + (nameBounds.y ?? 0);
package/src/utils/drag.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // utils/drag.ts
2
2
  import type { Shape } from '../types'
3
3
  import { getBounds, getDiagramRect, clampPointToRect } from './geom'
4
+ import { useGraphStore } from '../store/graphStore'
4
5
  import {
5
6
  finalRectOf,
6
7
  pickContainerByPointerTopmost,
@@ -57,7 +58,8 @@ export const stepSingleDrag = (
57
58
  dragOffset: { x: number; y: number } | null,
58
59
  dragBase: Record<string, Rect>,
59
60
  diagramId: string,
60
- constrainEdges: { left?: boolean; top?: boolean; right?: boolean; bottom?: boolean }
61
+ constrainEdges: { left?: boolean; top?: boolean; right?: boolean; bottom?: boolean },
62
+ hitPointer?: { x: number; y: number },
61
63
  ) => {
62
64
  const s = shapes.find(x => x.id === id); if (!s) return { ghost: {}, hover: null }
63
65
  const base = dragBase[id]; if (!base) return { ghost: {}, hover: null }
@@ -69,9 +71,10 @@ export const stepSingleDrag = (
69
71
 
70
72
  const dropRect: Rect = { x: nx, y: ny, width: base.width, height: base.height }
71
73
  // 按“鼠标所指”
74
+ const hp = hitPointer ?? pointer
72
75
  const candidate = pickContainerByPointerTopmost(
73
76
  shapes,
74
- pointer,
77
+ hp,
75
78
  id,
76
79
  diagramId,
77
80
  [id], // 如果是组拖可以传整个组的 id 列表
@@ -192,19 +195,21 @@ export const finalizeAfterTransform = (
192
195
  hoverId: string | null | undefined,
193
196
  updateShape: (id: string, updates: Partial<Shape>) => void,
194
197
  ): boolean => {
198
+ const graphStore = useGraphStore();
199
+ const byId = graphStore.shapeMap;
195
200
 
196
201
  // 深度工具:先处理“外层”再处理“内层”,避免内层已换父时反复扩父
197
202
  const depthOf = (sid: string) => {
198
203
  let d = 0
199
- let p = shapes.find(s => s.id === sid)?.parenShapeId ?? null
200
- while (p) { d++; p = shapes.find(s => s.id === p)?.parenShapeId ?? null }
204
+ let p = byId.get(sid)?.parenShapeId ?? null // ✅ O(1) 查找
205
+ while (p) { d++; p = byId.get(p)?.parenShapeId ?? null } // ✅ O(1) 查找
201
206
  return d
202
207
  }
203
208
  const sorted = [...changedIds].sort((a, b) => depthOf(a) - depthOf(b))
204
209
 
205
210
  let anyReparent = false
206
211
  for (const id of sorted) {
207
- const s = shapes.find(x => x.id === id); if (!s) continue
212
+ const s = byId.get(id); if (!s) continue // ✅ O(1) 查找
208
213
  const finalRect = finalRectOf(ghost, s)
209
214
  const did = resolveParentAfterDrag(
210
215
  shapes, s, finalRect,
@@ -3,6 +3,8 @@ import type { Shape } from "../types";
3
3
  import { pickTarget } from "./hittest";
4
4
  import { useGraphStore } from '../store/graphStore';
5
5
  import { snapPinToParentEdge } from './pinUtils';
6
+ import type { IHighlightUtils } from "../hooks/useHighlight";
7
+
6
8
 
7
9
  export class EdgeUtils {
8
10
  /**
@@ -610,10 +612,7 @@ export class EdgeUtils {
610
612
  targetShape: { value: any };
611
613
  showLine: { value: boolean };
612
614
  },
613
- highlightUtils: {
614
- highlightShape: (shape: any, highlight: boolean) => void;
615
- clearHighlightTimeout: () => void;
616
- }
615
+ highlightUtils: IHighlightUtils
617
616
  ) {
618
617
  // 1. 先停止连线状态,避免后续计算触发
619
618
  state.isConnecting.value = false;
@@ -77,7 +77,12 @@ export async function applyReparentAndClone(options: {
77
77
  const graphStore = useGraphStore()
78
78
  let ownerPayload: OwnerPayload | null = null
79
79
  const clonedIds: string[] = []
80
-
80
+ // 只在嵌套/脱离时刷新
81
+ const parentsToRefresh = new Set<string>();
82
+ const normPid = (pid: any): string | null => {
83
+ const v = pid ?? null;
84
+ return v === "" ? null : v;
85
+ };
81
86
  for (const id of changedIds) {
82
87
  const after = shapes.find(s => s.id === id)
83
88
  if (!after) continue
@@ -195,29 +200,28 @@ export async function applyReparentAndClone(options: {
195
200
  }
196
201
 
197
202
  // === 4) comparents:老父移除,新父添加 ===
198
- if (beforePid) {
199
- const oldP = shapes.find(s => s.id === beforePid)
200
- if (oldP && isShapeTypeNotInPackageTypes(oldP)) {
201
- removeChildShapeFromCompartment(oldP, after, updateShape)
202
- }
203
- }
204
- if (afterPid) {
205
- const newP = shapes.find(s => s.id === afterPid)
206
- if (newP && isShapeTypeNotInPackageTypes(newP)) {
207
- addChildShapeToCompartment(newP, after, updateShape)
208
- }
209
- }
203
+ // if (beforePid) {
204
+ // const oldP = shapes.find(s => s.id === beforePid)
205
+ // if (oldP && isShapeTypeNotInPackageTypes(oldP)) {
206
+ // removeChildShapeFromCompartment(oldP, after, updateShape)
207
+ // }
208
+ // }
209
+ // if (afterPid) {
210
+ // const newP = shapes.find(s => s.id === afterPid)
211
+ // if (newP && isShapeTypeNotInPackageTypes(newP)) {
212
+ // addChildShapeToCompartment(newP, after, updateShape)
213
+ // }
214
+ // }
210
215
  } else {
211
216
  // 父未变化:若父是隔间,确保 comparents 中有它(幂等)
212
- if (afterPid) {
213
- const p = shapes.find(s => s.id === afterPid)
214
- if (p && isShapeTypeNotInPackageTypes(p)) {
215
- addChildShapeToCompartment(p, after, updateShape)
216
- }
217
- }
217
+ // if (afterPid) {
218
+ // const p = shapes.find(s => s.id === afterPid)
219
+ // if (p && isShapeTypeNotInPackageTypes(p)) {
220
+ // addChildShapeToCompartment(p, after, updateShape)
221
+ // }
222
+ // }
218
223
  }
219
224
  }
220
-
221
225
  return { ownerPayload, clonedIds, cancelled: false }
222
226
  }
223
227
 
@@ -319,8 +323,8 @@ export const createOnNestDoneCallback = (options: {
319
323
  const afterParent = JSON.stringify(parent.bounds ?? {})
320
324
  if (afterParent == beforeParent) return
321
325
  // 只更新当前嵌套的父图元(parent)
322
- eventBus.emit('shape-size-update', [
323
- { id: parent.id, bounds: afterParent },
324
- ])
326
+ // eventBus.emit('shape-size-update', [
327
+ // { id: parent.id, bounds: afterParent },
328
+ // ])
325
329
  }
326
330
  }
@@ -18,43 +18,43 @@ export class IconLoader {
18
18
  let modules: Record<string, string>;
19
19
  switch (iconPath) {
20
20
  case 'menu':
21
- modules = import.meta.glob('/src/statics/icons/menu/*.{png,svg,jpg,jpeg,webp}', {
21
+ modules = import.meta.glob('../statics/icons/menu/*.{png,svg,jpg,jpeg,webp}', {
22
22
  eager: true,
23
23
  import: "default",
24
24
  }) as Record<string, string>;
25
25
  break;
26
26
  case 'home':
27
- modules = import.meta.glob('/src/statics/icons/home/*.{png,svg,jpg,jpeg,webp}', {
27
+ modules = import.meta.glob('../statics/icons/home/*.{png,svg,jpg,jpeg,webp}', {
28
28
  eager: true,
29
29
  import: "default",
30
30
  }) as Record<string, string>;
31
31
  break;
32
32
  case 'createElement':
33
- modules = import.meta.glob('/src/statics/icons/createElement/*.{png,svg,jpg,jpeg,webp}', {
33
+ modules = import.meta.glob('../statics/icons/createElement/*.{png,svg,jpg,jpeg,webp}', {
34
34
  eager: true,
35
35
  import: "default",
36
36
  }) as Record<string, string>;
37
37
  break;
38
38
  case 'createMenu':
39
- modules = import.meta.glob('/src/statics/icons/createMenu/*.{png,svg,jpg,jpeg,webp}', {
39
+ modules = import.meta.glob('../statics/icons/createMenu/*.{png,svg,jpg,jpeg,webp}', {
40
40
  eager: true,
41
41
  import: "default",
42
42
  }) as Record<string, string>;
43
43
  break;
44
44
  case 'modelView':
45
- modules = import.meta.glob('/src/statics/icons/modelView/*.{png,svg,jpg,jpeg,webp}', {
45
+ modules = import.meta.glob('../statics/icons/modelView/*.{png,svg,jpg,jpeg,webp}', {
46
46
  eager: true,
47
47
  import: "default",
48
48
  }) as Record<string, string>;
49
49
  break;
50
50
  case 'sideMenu':
51
- modules = import.meta.glob('/src/statics/icons/sideMenu/*.{png,svg,jpg,jpeg,webp}', {
51
+ modules = import.meta.glob('../statics/icons/sideMenu/*.{png,svg,jpg,jpeg,webp}', {
52
52
  eager: true,
53
53
  import: "default",
54
54
  }) as Record<string, string>;
55
55
  break;
56
56
  case 'childIcons':
57
- modules = import.meta.glob('/src/statics/icons/childIcons/*.{png,svg,jpg,jpeg,webp}', {
57
+ modules = import.meta.glob('../statics/icons/childIcons/*.{png,svg,jpg,jpeg,webp}', {
58
58
  eager: true,
59
59
  import: "default",
60
60
  }) as Record<string, string>;