@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.
- package/dist/index.d.ts +178 -10
- package/dist/index.esm.js +4944 -63227
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -38
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +10 -1
- package/src/components/ContextMenu/ContextMenu.vue +27 -13
- package/src/components/DiagramListTooltip/DiagramListTooltip.vue +7 -12
- package/src/components/InteractionLayer.vue +656 -496
- package/src/components/LineStyle/LineStyleMarker.vue +1 -1
- package/src/components/NameEditor/NameEditor.vue +212 -0
- package/src/components/SelectionBox/SelectionBox.vue +189 -0
- package/src/components/Shape/Block.vue +1 -1
- package/src/constants/edgeShapeKeys.ts +43 -3
- package/src/constants/index.ts +21 -4
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useHighlight.ts +223 -0
- package/src/hooks/useNameEdit.ts +234 -0
- package/src/{utils/resizeUtils.ts → hooks/useResize.ts} +55 -155
- package/src/index.ts +4 -1
- package/src/render/shape-renderer.ts +59 -46
- package/src/statics/icons/createMenu/show.png +0 -0
- package/src/statics/icons/createMenu/tree.png +0 -0
- 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
- package/src/statics/icons/createMenu//345/261/225/347/244/272/350/277/236/347/272/277@3x.png +0 -0
- package/src/statics/icons/createMenu//346/211/200/345/234/250/345/233/276/350/241/250@3x.png +0 -0
- package/src/store/graphStore.ts +185 -65
- package/src/types/index.ts +4 -2
- package/src/types/interactionLayer.ts +1 -0
- package/src/utils/batchAutoExpand.ts +65 -0
- package/src/utils/compartment.ts +78 -4
- package/src/utils/containers.ts +24 -10
- package/src/utils/contextMenuUtils.ts +126 -110
- package/src/utils/diagram.ts +19 -15
- package/src/utils/drag.ts +10 -5
- package/src/utils/edgeUtils.ts +3 -4
- package/src/utils/graphDragService.ts +27 -23
- package/src/utils/iconLoader.ts +7 -7
- package/src/utils/keyboardUtils.ts +221 -30
- package/src/utils/pinUtils.ts +1 -2
- package/src/utils/shapeOps/shapeOps.ts +168 -0
- package/src/utils/viewportCulling.ts +193 -0
- package/src/view/graph.vue +115 -60
- package/src/utils/highlightUtils.ts +0 -162
- package/src/utils/nameEditUtils.ts +0 -132
- package/src/utils/packgeMap.ts +0 -1
- /package/src/statics/icons/createMenu/{scissors.png → cut.png} +0 -0
package/src/store/graphStore.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineStore } from 'pinia'
|
|
2
|
-
import { ref, computed, shallowReactive, type Ref } from 'vue'
|
|
2
|
+
import { ref, computed, shallowReactive, nextTick, type Ref } from 'vue'
|
|
3
3
|
import type { Shape, OwnerPayload, ShapeSizeUpdatePayload } from '../types'
|
|
4
4
|
import { eventBus } from './eventBus'
|
|
5
5
|
import { getPolicy, checkNestViaFront, } from '../utils/policy'
|
|
@@ -17,15 +17,20 @@ import {
|
|
|
17
17
|
isShapeTypeNotInPackageTypes, removeChildShapeFromCompartment, addChildShapeToCompartment, hydrateComparentsFromJSON,
|
|
18
18
|
rebuildAllCompartmentChildrenAsShapes,
|
|
19
19
|
syncComparentShapesIntoRuntime,
|
|
20
|
+
syncShowComparentsByReparent,
|
|
20
21
|
} from '../utils/compartment'
|
|
21
22
|
import { EdgeUtils } from '../utils/edgeUtils'
|
|
22
23
|
import { expandParentByChild } from '../utils/autoExpandParent'
|
|
24
|
+
import { batchAutoExpandParents } from '../utils/batchAutoExpand'
|
|
23
25
|
import { adjustCanvasToFitAllShapes } from '../utils/diagram'
|
|
24
26
|
import { applyReparentAndClone, autoExpandMovedCompartmentsAfterDrag, buildDragEndPayloads, buildPrevParentMap, collectAffectedShapeIds, createOnNestDoneCallback } from '../utils/graphDragService'
|
|
27
|
+
import { createShapeOperator, type ShapeOp, type ShapeId } from "../utils/shapeOps/shapeOps"
|
|
25
28
|
type Rect = { x: number; y: number; width: number; height: number };
|
|
26
29
|
export const useGraphStore = defineStore('graph', () => {
|
|
27
30
|
// 状态
|
|
28
31
|
const shapes = ref<Shape[]>([])
|
|
32
|
+
// 在 store 初始化时创建一次
|
|
33
|
+
const { applyShapeOp } = createShapeOperator<Shape>()
|
|
29
34
|
// 缩放比例 - 按画布ID存储
|
|
30
35
|
const scales = ref<Record<string, number>>({});
|
|
31
36
|
const selectedShape = ref<Shape | null>(null)
|
|
@@ -63,12 +68,51 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
63
68
|
const activeDiagramId = ref<string | null>(null);
|
|
64
69
|
// 当前被选中的多个 id
|
|
65
70
|
const selectedIds = ref<string[]>([])
|
|
66
|
-
|
|
67
|
-
// 外部拖拽创建中的形状 id(用于隐藏实际形状,只显示 ghost)
|
|
71
|
+
// 外部拖拽创建中的形状 id(用于隐藏实际形状,只显示 ghost)
|
|
68
72
|
const externalCreatingId = ref<string | null>(null)
|
|
73
|
+
// 剪切状态的图元 ID 集合(用于 SVG 遮盖层渲染)
|
|
74
|
+
const cutShapeIds = ref<Set<string>>(new Set())
|
|
75
|
+
// 剪贴板中图元的数量(用于工具栏按钮状态同步)
|
|
76
|
+
const copiedShapesCount = ref<number>(0)
|
|
77
|
+
|
|
69
78
|
// 计算属性
|
|
70
79
|
const shapeCount = computed(() => shapes.value.length)
|
|
71
80
|
const hasSelectedShape = computed(() => selectedShape.value !== null)
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 父子关系索引映射
|
|
84
|
+
* 用于快速查找某个图元的所有直接子图元
|
|
85
|
+
* key: 父图元 ID, value: 子图元 ID 数组
|
|
86
|
+
* 性能优化: O(n) 构建, O(1) 查询
|
|
87
|
+
*/
|
|
88
|
+
const parentChildMap = computed(() => {
|
|
89
|
+
const map = new Map<string, string[]>();
|
|
90
|
+
shapes.value.forEach(shape => {
|
|
91
|
+
if (shape.parenShapeId) {
|
|
92
|
+
if (!map.has(shape.parenShapeId)) {
|
|
93
|
+
map.set(shape.parenShapeId, []);
|
|
94
|
+
}
|
|
95
|
+
map.get(shape.parenShapeId)!.push(shape.id);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return map;
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 图元ID索引映射
|
|
103
|
+
* 用于快速通过ID查找图元对象
|
|
104
|
+
* key: 图元 ID, value: 图元对象
|
|
105
|
+
* 性能优化: O(n) 构建, O(1) 查询
|
|
106
|
+
* 解决嵌套图元场景下大量 find() 调用导致的性能问题
|
|
107
|
+
*/
|
|
108
|
+
const shapeMap = computed(() => {
|
|
109
|
+
const map = new Map<string, Shape>();
|
|
110
|
+
shapes.value.forEach(shape => {
|
|
111
|
+
map.set(shape.id, shape);
|
|
112
|
+
});
|
|
113
|
+
return map;
|
|
114
|
+
})
|
|
115
|
+
|
|
72
116
|
//图元shapeKey
|
|
73
117
|
const taggedValueLabels = ref<string[]>([]) // 隔间组件的图元类型
|
|
74
118
|
const packagesTypes = ref<string[]>([]) // 需要展示线的隔间组件的图元类型
|
|
@@ -83,9 +127,11 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
83
127
|
/**
|
|
84
128
|
* 添加图元
|
|
85
129
|
* @param shape - The shape to add
|
|
130
|
+
* @param options - Optional configuration
|
|
131
|
+
* @param options.autoExpandParent - Whether to auto-expand parent container (default: true)
|
|
86
132
|
*/
|
|
87
133
|
// 添加图元
|
|
88
|
-
const addShape = (shape: Shape) => {
|
|
134
|
+
const addShape = (shape: Shape, options?: { autoExpandParent?: boolean }) => {
|
|
89
135
|
// 检查是否是边类型且已经存在相同类型、相同源和目标的边
|
|
90
136
|
if (shape.shapeType === 'edge' && shape.sourceId && shape.targetId) {
|
|
91
137
|
const existingEdge = shapes.value.find(
|
|
@@ -104,6 +150,15 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
104
150
|
shapes.value.push(shape)
|
|
105
151
|
// 通过事件总线发送事件
|
|
106
152
|
eventBus.emit('shape-added', shape)
|
|
153
|
+
|
|
154
|
+
// 自动扩父逻辑:如果图元有父元素且需要自动扩父,触发扩父逻辑
|
|
155
|
+
const shouldAutoExpand = options?.autoExpandParent !== false // 默认为 true
|
|
156
|
+
if (shouldAutoExpand && shape.parenShapeId && shape.bounds) {
|
|
157
|
+
// 使用 nextTick 确保图元已经完全添加
|
|
158
|
+
nextTick(() => {
|
|
159
|
+
expandParentAndEmitSizeUpdateByChild(shape)
|
|
160
|
+
})
|
|
161
|
+
}
|
|
107
162
|
}
|
|
108
163
|
//移除图元
|
|
109
164
|
const removeShape = (shapeId: string) => {
|
|
@@ -113,12 +168,12 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
113
168
|
// 通过事件总线发送事件
|
|
114
169
|
// eventBus.emit('shape-removed', removedShape)
|
|
115
170
|
// 若该图元有父,且父是隔间:从 comparents 里移除这个子 Shape
|
|
116
|
-
if (removedShape.parenShapeId) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
171
|
+
// if (removedShape.parenShapeId) {
|
|
172
|
+
// const parent = shapes.value.find(s => s.id == removedShape.parenShapeId)
|
|
173
|
+
// if (parent && isShapeTypeNotInPackageTypes(parent)) {
|
|
174
|
+
// removeChildShapeFromCompartment(parent, removedShape, updateShape)
|
|
175
|
+
// }
|
|
176
|
+
// }
|
|
122
177
|
// 如果删除的是当前选中的形状,清除选中状态
|
|
123
178
|
if (selectedShape.value?.id === shapeId) {
|
|
124
179
|
selectedShape.value = null
|
|
@@ -138,8 +193,8 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
138
193
|
}
|
|
139
194
|
function refreshChildSnapshotInCompartment(parent: Shape, child: Shape) {
|
|
140
195
|
// 直接“先移后加”达到“替换快照”的效果(工具内部会用快照,避免 Proxy/循环)
|
|
141
|
-
removeChildShapeFromCompartment(parent, child, updateShapeRaw)
|
|
142
|
-
addChildShapeToCompartment(parent, child, updateShapeRaw)
|
|
196
|
+
// removeChildShapeFromCompartment(parent, child, updateShapeRaw)
|
|
197
|
+
// addChildShapeToCompartment(parent, child, updateShapeRaw)
|
|
143
198
|
}
|
|
144
199
|
|
|
145
200
|
// 用于画布渲染的可见图元列表
|
|
@@ -190,28 +245,28 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
190
245
|
|
|
191
246
|
const newPid = after.parenShapeId ?? null
|
|
192
247
|
// 父变了:从旧父移除、向新父添加(工具内部有去重/幂等)
|
|
193
|
-
if (oldPid !== newPid) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
248
|
+
// if (oldPid !== newPid) {
|
|
249
|
+
// if (oldPid) {
|
|
250
|
+
// const oldP = shapes.value.find(s => s.id === oldPid)
|
|
251
|
+
// if (oldP && isShapeTypeNotInPackageTypes(oldP)) {
|
|
252
|
+
// removeChildShapeFromCompartment(oldP, after, updateShapeRaw)
|
|
253
|
+
// }
|
|
254
|
+
// }
|
|
255
|
+
// if (newPid) {
|
|
256
|
+
// const newP = shapes.value.find(s => s.id === newPid)
|
|
257
|
+
// if (newP && isShapeTypeNotInPackageTypes(newP)) {
|
|
258
|
+
// // addChildShapeToCompartment(newP, after, updateShapeRaw)
|
|
259
|
+
// }
|
|
260
|
+
// }
|
|
261
|
+
// return
|
|
262
|
+
// }
|
|
208
263
|
// 父没变 & 在隔间里:刷新快照(属性变化时也要同步到 comparentShapes)
|
|
209
|
-
if (newPid) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
264
|
+
// if (newPid) {
|
|
265
|
+
// const p = shapes.value.find(s => s.id === newPid)
|
|
266
|
+
// if (p && isShapeTypeNotInPackageTypes(p)) {
|
|
267
|
+
// refreshChildSnapshotInCompartment(p, after)
|
|
268
|
+
// }
|
|
269
|
+
// }
|
|
215
270
|
}
|
|
216
271
|
const sameId = (a?: string | null, b?: string | null) =>
|
|
217
272
|
(a ?? null) === (b ?? null)
|
|
@@ -243,11 +298,13 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
243
298
|
? pendingNestedIds.value
|
|
244
299
|
: []
|
|
245
300
|
const pendingSet = new Set(pendingList)
|
|
301
|
+
// 用 shapeMap O(1) 查找原始图元,避免 find O(N)
|
|
302
|
+
const map = shapeMap.value
|
|
246
303
|
for (const id in dragGhost) {
|
|
247
|
-
// 如果当前 id 在 pending 里,不生成 ghost
|
|
248
304
|
if (pendingSet.has(id)) continue
|
|
249
|
-
const orig =
|
|
305
|
+
const orig = map.get(id)
|
|
250
306
|
if (!orig) continue
|
|
307
|
+
|
|
251
308
|
out.push({
|
|
252
309
|
...orig,
|
|
253
310
|
bounds: { ...dragGhost[id] },
|
|
@@ -280,14 +337,36 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
280
337
|
eventBus.emit('title-changed', title)
|
|
281
338
|
}
|
|
282
339
|
|
|
283
|
-
|
|
284
|
-
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 批量操作图元(新增 / 修改 / 删除)
|
|
343
|
+
* @param payload - 新增/修改时传 Shape[];删除时可传 Shape[](只需要 id)或 id 数组
|
|
344
|
+
* @param op - 操作类型:add | update | delete | upsert(默认)
|
|
345
|
+
* @param options - 额外选项(保持你原来的 autoExpandParents)
|
|
346
|
+
*/
|
|
347
|
+
const updateShapes = (
|
|
348
|
+
payload: Shape[] | Array<string | number>,
|
|
349
|
+
op: ShapeOp = "upsert",
|
|
350
|
+
options?: { autoExpandParents?: boolean }
|
|
351
|
+
) => {
|
|
285
352
|
// 先把 comparents[*].comparentShapes 铺平同步到运行时
|
|
286
|
-
const expanded = syncComparentShapesIntoRuntime(newShapes)
|
|
287
|
-
|
|
353
|
+
// const expanded = syncComparentShapesIntoRuntime(newShapes)
|
|
354
|
+
const { changedShapes } = applyShapeOp({
|
|
355
|
+
list: shapes.value,
|
|
356
|
+
payload: payload as any, // delete 时是 id[],其余是 Shape[]
|
|
357
|
+
op,
|
|
358
|
+
})
|
|
288
359
|
pendingNestedIds.value = []
|
|
289
|
-
eventBus.emit('shapes-updated',
|
|
290
|
-
hydrateAllComparents()
|
|
360
|
+
// eventBus.emit('shapes-updated', newShapes)
|
|
361
|
+
// hydrateAllComparents()
|
|
362
|
+
|
|
363
|
+
// 批量自动扩展父容器(默认启用)
|
|
364
|
+
const shouldAutoExpand = options?.autoExpandParents !== false
|
|
365
|
+
if (shouldAutoExpand && changedShapes.length > 0) {
|
|
366
|
+
nextTick(() => {
|
|
367
|
+
batchAutoExpandParents(changedShapes, updateShape)
|
|
368
|
+
})
|
|
369
|
+
}
|
|
291
370
|
}
|
|
292
371
|
// 获取当前图元数据
|
|
293
372
|
const getShapes = () => {
|
|
@@ -303,27 +382,26 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
303
382
|
}
|
|
304
383
|
|
|
305
384
|
/** 把所有隔间父的 comparents 从 comparentsJSON 还原;若没有 JSON 就按 parenShapeId 兜底重建 */
|
|
306
|
-
const hydrateAllComparents = () => {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
385
|
+
// const hydrateAllComparents = () => {
|
|
386
|
+
// if (comparentsHydratedOnce.value) return
|
|
387
|
+
// if (!shapes.value.length) return
|
|
388
|
+
// let hadAnyJSON = false
|
|
389
|
+
// for (const p of shapes.value) {
|
|
390
|
+
// if (isShapeTypeNotInPackageTypes(p)) {
|
|
391
|
+
// const any = p as any
|
|
392
|
+
// if (typeof any.comparentsJSON === 'string' && any.comparentsJSON.length) {
|
|
393
|
+
// hydrateComparentsFromJSON(p, updateShape)
|
|
394
|
+
// hadAnyJSON = true
|
|
395
|
+
// }
|
|
396
|
+
// }
|
|
397
|
+
// }
|
|
398
|
+
// // 历史数据可能还没有 comparentsJSON:按父子关系兜底重建一次
|
|
399
|
+
// if (!hadAnyJSON) {
|
|
400
|
+
// rebuildAllCompartmentChildrenAsShapes(shapes.value, updateShape)
|
|
401
|
+
// }
|
|
402
|
+
|
|
403
|
+
// comparentsHydratedOnce.value = true
|
|
404
|
+
// }
|
|
327
405
|
//拖动元素相关操作
|
|
328
406
|
// 开始拖动已有元素(仅对 shape 生效)
|
|
329
407
|
const byId = (id: string) => shapes.value.find(s => s.id === id) || null
|
|
@@ -468,7 +546,10 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
468
546
|
}
|
|
469
547
|
}
|
|
470
548
|
//拖动过程中
|
|
471
|
-
const moveDraggedShape = async (
|
|
549
|
+
const moveDraggedShape = async (
|
|
550
|
+
pointer: { x: number; y: number },
|
|
551
|
+
options?: { hitPointer?: { x: number; y: number } }
|
|
552
|
+
) => {
|
|
472
553
|
if (!isDragging.value || draggingIds.value.length === 0) return
|
|
473
554
|
|
|
474
555
|
if (draggingIds.value.length === 1) {
|
|
@@ -481,7 +562,8 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
481
562
|
dragOffset.value,
|
|
482
563
|
dragBase.value,
|
|
483
564
|
currentDiagramId.value || s.diagramId,
|
|
484
|
-
getPolicy(s).constrainToDiagram
|
|
565
|
+
getPolicy(s).constrainToDiagram,
|
|
566
|
+
options?.hitPointer
|
|
485
567
|
)
|
|
486
568
|
// 就地写 shallowReactive,不再每帧解构新对象
|
|
487
569
|
for (const k in ghost) dragGhost[k] = ghost[k]
|
|
@@ -589,6 +671,13 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
589
671
|
})
|
|
590
672
|
//对“本次直接拖动的隔间”做一次自动扩容检查
|
|
591
673
|
autoExpandMovedCompartmentsAfterDrag(shapes.value, changedIds, updateShape)
|
|
674
|
+
// 处理comparents字段
|
|
675
|
+
syncShowComparentsByReparent(
|
|
676
|
+
shapes.value,
|
|
677
|
+
changedIds,
|
|
678
|
+
prevParentById,
|
|
679
|
+
(id, u) => updateShapeRaw(id, u)
|
|
680
|
+
)
|
|
592
681
|
// 收集受影响的所有 shape(自身 + 父 + 子树 + 祖先)
|
|
593
682
|
const affectedIds = collectAffectedShapeIds(shapes.value, changedIds, clonedIds)
|
|
594
683
|
//组装 payloads(深拷贝),给更新接口用
|
|
@@ -616,7 +705,8 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
616
705
|
}
|
|
617
706
|
}
|
|
618
707
|
}
|
|
619
|
-
|
|
708
|
+
console.log("drag end payloads:", payloads, ownerPayload);
|
|
709
|
+
|
|
620
710
|
//对外只暴露一个事件:shape-drag-end
|
|
621
711
|
eventBus.emit('shape-drag-end', payloads, ownerPayload, onNestDone)
|
|
622
712
|
|
|
@@ -782,6 +872,29 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
782
872
|
museInGraphView.value = isIn
|
|
783
873
|
}
|
|
784
874
|
|
|
875
|
+
/**
|
|
876
|
+
* 设置剪切状态的图元 ID
|
|
877
|
+
* @param ids 要设置为剪切状态的图元 ID 数组
|
|
878
|
+
*/
|
|
879
|
+
const setCutShapeIds = (ids: string[]) => {
|
|
880
|
+
cutShapeIds.value = new Set(ids)
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* 清除所有剪切状态
|
|
885
|
+
*/
|
|
886
|
+
const clearCutShapeIds = () => {
|
|
887
|
+
cutShapeIds.value = new Set()
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* 设置剪贴板图元数量
|
|
892
|
+
* @param count 剪贴板中图元的数量
|
|
893
|
+
*/
|
|
894
|
+
const setCopiedShapesCount = (count: number) => {
|
|
895
|
+
copiedShapesCount.value = count
|
|
896
|
+
}
|
|
897
|
+
|
|
785
898
|
return {
|
|
786
899
|
// 状态
|
|
787
900
|
shapes,
|
|
@@ -796,6 +909,8 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
796
909
|
currentDiagramId,
|
|
797
910
|
canDropOnCanvas,
|
|
798
911
|
canOperate,
|
|
912
|
+
cutShapeIds, // 剪切状态的图元 ID 集合
|
|
913
|
+
copiedShapesCount, // 剪贴板中图元的数量
|
|
799
914
|
//图元shapeKey
|
|
800
915
|
taggedValueLabels,
|
|
801
916
|
packagesTypes,
|
|
@@ -806,6 +921,8 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
806
921
|
// 计算属性
|
|
807
922
|
shapeCount,
|
|
808
923
|
hasSelectedShape,
|
|
924
|
+
parentChildMap, // 父子关系索引映射
|
|
925
|
+
shapeMap, // 图元ID索引映射 (性能优化)
|
|
809
926
|
marqueeShapes,
|
|
810
927
|
ghostShadow,
|
|
811
928
|
scales,
|
|
@@ -847,6 +964,9 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
847
964
|
externalCreatingId,
|
|
848
965
|
expandParentAndEmitSizeUpdateByChild,
|
|
849
966
|
setIsMouseInGraphView,
|
|
967
|
+
setCutShapeIds,
|
|
968
|
+
clearCutShapeIds,
|
|
969
|
+
setCopiedShapesCount,
|
|
850
970
|
}
|
|
851
971
|
},
|
|
852
972
|
//配置持久化
|
package/src/types/index.ts
CHANGED
|
@@ -104,7 +104,8 @@ export interface Shape {
|
|
|
104
104
|
comparents?: CompartmentRecord[] // 隔间组件数组
|
|
105
105
|
meta?: any; // 高亮信息
|
|
106
106
|
scenarioMenus?: scenarioMenu[],
|
|
107
|
-
inert?: boolean | true //
|
|
107
|
+
inert?: boolean | true // 惰性可见(不可见/不渲染/不拾取)
|
|
108
|
+
isCut?: boolean // 标记图元是否被剪切
|
|
108
109
|
sourceModels?: string[] // 允许的源图元数组
|
|
109
110
|
targetModels?: string[] // 允许的目标图元数组
|
|
110
111
|
modelTypePropertyId: string // 设置类型
|
|
@@ -203,9 +204,10 @@ export interface ShapeSizeUpdatePayload {
|
|
|
203
204
|
|
|
204
205
|
// 所在图表列表
|
|
205
206
|
export interface locationChart {
|
|
206
|
-
modelCode: string
|
|
207
|
+
modelCode: string
|
|
207
208
|
treeId: string
|
|
208
209
|
modelName: string //图表名称
|
|
210
|
+
modelIcon: string //图表图标
|
|
209
211
|
}
|
|
210
212
|
|
|
211
213
|
// InteractionLayer types
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Shape } from '@/types'
|
|
2
|
+
import { ensureParentFitsChildren } from './containers'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 批量自动扩展所有父容器以适应其子元素
|
|
6
|
+
* - 按深度从深到浅处理,确保嵌套关系正确
|
|
7
|
+
* - 只处理需要扩展的父容器,避免不必要的计算
|
|
8
|
+
*
|
|
9
|
+
* @param shapes - 所有图元数据
|
|
10
|
+
* @param updateShape - 更新图元的函数
|
|
11
|
+
*/
|
|
12
|
+
export function batchAutoExpandParents(
|
|
13
|
+
shapes: Shape[],
|
|
14
|
+
updateShape: (id: string, updates: Partial<Shape>) => void
|
|
15
|
+
): void {
|
|
16
|
+
// 1. 收集所有有子元素的父容器
|
|
17
|
+
const parentIds = new Set<string>()
|
|
18
|
+
shapes.forEach(shape => {
|
|
19
|
+
if (shape.parenShapeId) {
|
|
20
|
+
parentIds.add(shape.parenShapeId)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (parentIds.size === 0) return
|
|
25
|
+
|
|
26
|
+
// 2. 按深度排序(深度大的先处理,从内到外)
|
|
27
|
+
const parentsWithDepth = Array.from(parentIds)
|
|
28
|
+
.map(id => ({
|
|
29
|
+
id,
|
|
30
|
+
depth: getDepth(shapes, id),
|
|
31
|
+
shape: shapes.find(s => s.id === id)
|
|
32
|
+
}))
|
|
33
|
+
.filter(p => p.shape)
|
|
34
|
+
.sort((a, b) => b.depth - a.depth) // 深度大的在前
|
|
35
|
+
|
|
36
|
+
// 3. 从最深的父容器开始,逐个扩展
|
|
37
|
+
parentsWithDepth.forEach(({ shape }) => {
|
|
38
|
+
if (shape) {
|
|
39
|
+
ensureParentFitsChildren(shapes, shape, updateShape)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 计算图元的嵌套深度
|
|
46
|
+
* @param shapes - 所有图元数据
|
|
47
|
+
* @param id - 要计算深度的图元 ID
|
|
48
|
+
* @returns 嵌套深度(0 表示顶层)
|
|
49
|
+
*/
|
|
50
|
+
function getDepth(shapes: Shape[], id: string): number {
|
|
51
|
+
let depth = 0
|
|
52
|
+
let current = shapes.find(s => s.id === id)
|
|
53
|
+
|
|
54
|
+
while (current?.parenShapeId) {
|
|
55
|
+
depth++
|
|
56
|
+
current = shapes.find(s => s.id === current!.parenShapeId)
|
|
57
|
+
// 防止循环引用导致无限循环
|
|
58
|
+
if (depth > 100) {
|
|
59
|
+
console.warn(`检测到可能的循环引用,图元 ID: ${id}`)
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return depth
|
|
65
|
+
}
|
package/src/utils/compartment.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Bounds, CompartmentRecord, Shape } from '@/types'
|
|
3
3
|
import _ from 'lodash'
|
|
4
4
|
import { toRaw } from 'vue';
|
|
5
|
+
import { useGraphStore } from '../store/graphStore';
|
|
5
6
|
|
|
6
7
|
export type Rect = { x: number; y: number; width: number; height: number }
|
|
7
8
|
export type Zones = {
|
|
@@ -380,7 +381,8 @@ export function hydrateComparentsFromJSON(
|
|
|
380
381
|
parent: Shape,
|
|
381
382
|
updateShape: (id: string, u: Partial<Shape>) => void
|
|
382
383
|
) {
|
|
383
|
-
|
|
384
|
+
debugger
|
|
385
|
+
// if (!isCompartment(parent)) return
|
|
384
386
|
const json = ensureComparentsJSON(parent)
|
|
385
387
|
const recs = parseComparentsJSONCompat(json, parent)
|
|
386
388
|
// 运行时直接等于记录数组
|
|
@@ -452,18 +454,21 @@ export function removeChildShapeFromCompartment(
|
|
|
452
454
|
writeComparentsJSONFromRuntime(parent, updateShape)
|
|
453
455
|
}
|
|
454
456
|
|
|
455
|
-
/**
|
|
457
|
+
/** 兜底:按 parenShapeId 重建 comparents[0].comparentShapes(启动/恢复后) */
|
|
456
458
|
export function rebuildAllCompartmentChildrenAsShapes(
|
|
457
459
|
shapes: Shape[],
|
|
458
460
|
updateShape: (id: string, u: Partial<Shape>) => void
|
|
459
461
|
) {
|
|
462
|
+
const graphStore = useGraphStore();
|
|
463
|
+
|
|
460
464
|
for (const p of shapes) {
|
|
461
465
|
if (!isShapeTypeNotInPackageTypes(p)) continue
|
|
462
466
|
|
|
463
|
-
const
|
|
467
|
+
const childIds = graphStore.parentChildMap.get(p.id) || [];
|
|
468
|
+
const children = childIds.map(id => shapes.find(s => s.id === id)).filter(Boolean) as Shape[];
|
|
464
469
|
const snaps = children.map(makePersistableSnapshot)
|
|
465
470
|
|
|
466
|
-
//
|
|
471
|
+
// 运行时:确保有记录,再覆盖其 comparentShapes
|
|
467
472
|
const list = ensureComparentsArray(p)
|
|
468
473
|
const rec = list[0] || emptyRecord(p)
|
|
469
474
|
rec.modelId = ''
|
|
@@ -531,4 +536,73 @@ function calcShowComparentsFlag(parent: Shape): boolean {
|
|
|
531
536
|
if (!Array.isArray(comps) || comps.length === 0) return false
|
|
532
537
|
const rec = comps[0]
|
|
533
538
|
return Array.isArray(rec?.comparentShapes) && rec.comparentShapes.length > 0
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* 仅在“嵌套/脱离父(parenShapeId 变化)”后调用:
|
|
542
|
+
* - changedIds:本次发生位移/可能换父的子 id 集合
|
|
543
|
+
* - prevParentById:拖拽前这些子对应的父 id 映射(oldPid)
|
|
544
|
+
* - update:建议传 updateShapeRaw(更轻,不触发边更新)
|
|
545
|
+
*
|
|
546
|
+
* 性能:只遍历 shapes 一次 O(N),且只更新少量父节点 O(K)
|
|
547
|
+
*/
|
|
548
|
+
export function syncShowComparentsByReparent(
|
|
549
|
+
shapes: Shape[],
|
|
550
|
+
changedIds: string[],
|
|
551
|
+
prevParentById: Record<string, string | null | undefined>,
|
|
552
|
+
update: (id: string, updates: Partial<Shape>) => void,
|
|
553
|
+
options?: {
|
|
554
|
+
/** 是否忽略 edge/diagram 作为子元素(默认 true) */
|
|
555
|
+
ignoreEdgeAndDiagram?: boolean;
|
|
556
|
+
}
|
|
557
|
+
) {
|
|
558
|
+
const ignoreEdgeAndDiagram = options?.ignoreEdgeAndDiagram !== false;
|
|
559
|
+
|
|
560
|
+
// 收集“父发生变化”的 oldPid/newPid(去重)
|
|
561
|
+
const parents = new Set<string>();
|
|
562
|
+
|
|
563
|
+
for (const id of changedIds) {
|
|
564
|
+
const oldPid = (prevParentById[id] ?? "") as string;
|
|
565
|
+
const cur = shapes.find((s) => s.id === id);
|
|
566
|
+
const newPid = ((cur as any)?.parenShapeId ?? "") as string;
|
|
567
|
+
|
|
568
|
+
// 只有父变化才触发(满足你“不是每次都触发”)
|
|
569
|
+
if (oldPid === newPid) continue;
|
|
570
|
+
|
|
571
|
+
if (oldPid) parents.add(oldPid);
|
|
572
|
+
if (newPid) parents.add(newPid);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (parents.size === 0) return;
|
|
576
|
+
|
|
577
|
+
// 统计这些父各自是否还有子(只扫 shapes 一次)
|
|
578
|
+
const childCount = new Map<string, number>();
|
|
579
|
+
parents.forEach((pid) => childCount.set(pid, 0));
|
|
580
|
+
|
|
581
|
+
for (const s of shapes) {
|
|
582
|
+
const pid = ((s as any).parenShapeId ?? "") as string;
|
|
583
|
+
if (!pid || !parents.has(pid)) continue;
|
|
584
|
+
|
|
585
|
+
if (ignoreEdgeAndDiagram) {
|
|
586
|
+
const st = String((s as any).shapeType || "").toLowerCase();
|
|
587
|
+
if (st === "edge" || st === "diagram") continue;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// 防御:不把自己算作自己的子
|
|
591
|
+
if (s.id === pid) continue;
|
|
592
|
+
|
|
593
|
+
childCount.set(pid, (childCount.get(pid) || 0) + 1);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// 批量回写 showComparents(只更新受影响的父)
|
|
597
|
+
for (const pid of parents) {
|
|
598
|
+
const parent = shapes.find((x) => x.id === pid);
|
|
599
|
+
if (!parent) continue;
|
|
600
|
+
|
|
601
|
+
const shouldShow = (childCount.get(pid) || 0) > 0;
|
|
602
|
+
|
|
603
|
+
// 避免重复写入导致多余渲染
|
|
604
|
+
if ((parent as any).showComparents === shouldShow) continue;
|
|
605
|
+
|
|
606
|
+
update(pid, { showComparents: shouldShow } as any);
|
|
607
|
+
}
|
|
534
608
|
}
|