@mx-sose-front/mx-sose-graph 1.1.3 → 1.1.5
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 +177 -9
- package/dist/index.esm.js +4569 -63119
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -39
- 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 +10 -10
- package/src/components/DiagramListTooltip/DiagramListTooltip.vue +6 -11
- package/src/components/InteractionLayer.vue +323 -157
- package/src/components/LineStyle/LineStyleMarker.vue +1 -1
- package/src/components/{NameEditor.vue → NameEditor/NameEditor.vue} +4 -4
- package/src/components/{SelectionBox.vue → SelectionBox/SelectionBox.vue} +5 -5
- package/src/components/Shape/Block.vue +1 -1
- package/src/constants/edgeShapeKeys.ts +43 -3
- package/src/constants/index.ts +19 -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 +106 -147
- 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 +195 -32
- 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 -137
- /package/src/statics/icons/createMenu/{scissors.png → cut.png} +0 -0
package/src/utils/containers.ts
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
79
|
+
p = byId.get(p)?.parenShapeId ?? null // ✅ O(1) 查找,替代 find()
|
|
77
80
|
}
|
|
78
81
|
return false
|
|
79
82
|
}
|
|
80
83
|
|
|
81
|
-
/**
|
|
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
|
|
88
|
-
|
|
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
|
|
160
|
-
|
|
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
|
-
|
|
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.
|
|
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,50 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
2
|
import { eventBus } from '../store';
|
|
3
|
+
import { useGraphStore } from '../store/graphStore';
|
|
3
4
|
import _ from 'lodash';
|
|
4
|
-
import { getUuid } from '../utils/index';
|
|
5
|
-
|
|
6
|
-
// 菜单位置接口
|
|
7
|
-
export interface MenuPosition {
|
|
8
|
-
x: number;
|
|
9
|
-
y: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// 菜单项配置接口
|
|
13
|
-
export interface MenuItemConfig {
|
|
14
|
-
id: string;
|
|
15
|
-
label: string;
|
|
16
|
-
icon?: string;
|
|
17
|
-
disabled?: boolean;
|
|
18
|
-
divider?: boolean;
|
|
19
|
-
handler?: () => void;
|
|
20
|
-
submenu?: MenuItemConfig[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 菜单配置接口
|
|
24
|
-
export interface MenuConfig {
|
|
25
|
-
items: MenuItemConfig[];
|
|
26
|
-
width?: number;
|
|
27
|
-
itemHeight?: number;
|
|
28
|
-
padding?: number;
|
|
29
|
-
maxVisibleItems?: number;
|
|
30
|
-
safeMargin?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 默认菜单配置
|
|
34
|
-
const DEFAULT_MENU_CONFIG: Required<MenuConfig> = {
|
|
35
|
-
items: [],
|
|
36
|
-
width: 180,
|
|
37
|
-
itemHeight: 36,
|
|
38
|
-
padding: 6,
|
|
39
|
-
maxVisibleItems: 8,
|
|
40
|
-
safeMargin: 10
|
|
41
|
-
};
|
|
42
5
|
|
|
43
6
|
// 右键菜单工具函数
|
|
44
7
|
export class ContextMenuUtils {
|
|
45
8
|
// 用于存储复制的图元
|
|
46
9
|
private static copiedShapes: any[] = [];
|
|
47
|
-
|
|
10
|
+
// 用于标记操作类型:'copy' 或 'cut'
|
|
11
|
+
private static operationType: 'copy' | 'cut' = 'copy';
|
|
12
|
+
|
|
48
13
|
/**
|
|
49
14
|
* 处理右键菜单点击事件
|
|
50
15
|
* @param event 鼠标事件
|
|
@@ -63,7 +28,8 @@ export class ContextMenuUtils {
|
|
|
63
28
|
shapes: any[],
|
|
64
29
|
selectShape?: (shape: any) => void,
|
|
65
30
|
isDragging?: boolean,
|
|
66
|
-
isResizing?: boolean
|
|
31
|
+
isResizing?: boolean,
|
|
32
|
+
scale: number = 1
|
|
67
33
|
): any | null {
|
|
68
34
|
// 如果图元正在移动或缩放,则不显示菜单
|
|
69
35
|
if (isDragging || isResizing) {
|
|
@@ -72,8 +38,8 @@ export class ContextMenuUtils {
|
|
|
72
38
|
|
|
73
39
|
event.preventDefault();
|
|
74
40
|
|
|
75
|
-
//
|
|
76
|
-
const point = ContextMenuUtils.toLocalPoint(event, layerRef.value);
|
|
41
|
+
// 获取点击位置,考虑缩放比例
|
|
42
|
+
const point = ContextMenuUtils.toLocalPoint(event, layerRef.value, scale);
|
|
77
43
|
|
|
78
44
|
// 使用命中测试找到点击的图元
|
|
79
45
|
const hit = pickTarget(shapes, point);
|
|
@@ -91,16 +57,18 @@ export class ContextMenuUtils {
|
|
|
91
57
|
|
|
92
58
|
/**
|
|
93
59
|
* 将鼠标事件坐标转换为本地坐标
|
|
60
|
+
* 注意:需要考虑画布的缩放比例
|
|
94
61
|
*/
|
|
95
|
-
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 } {
|
|
96
63
|
if (!layerRef) {
|
|
97
64
|
return { x: event.clientX, y: event.clientY };
|
|
98
65
|
}
|
|
99
66
|
|
|
100
67
|
const rect = layerRef.getBoundingClientRect();
|
|
68
|
+
// 计算本地坐标并除以缩放比例
|
|
101
69
|
return {
|
|
102
|
-
x: event.clientX - rect.left,
|
|
103
|
-
y: event.clientY - rect.top
|
|
70
|
+
x: (event.clientX - rect.left) / scale,
|
|
71
|
+
y: (event.clientY - rect.top) / scale
|
|
104
72
|
};
|
|
105
73
|
}
|
|
106
74
|
|
|
@@ -116,9 +84,9 @@ export class ContextMenuUtils {
|
|
|
116
84
|
* 计算菜单位置(避免超出屏幕边界)
|
|
117
85
|
*/
|
|
118
86
|
static calculateMenuPosition(
|
|
119
|
-
x: number,
|
|
120
|
-
y: number,
|
|
121
|
-
menuWidth: number = 180,
|
|
87
|
+
x: number,
|
|
88
|
+
y: number,
|
|
89
|
+
menuWidth: number = 180,
|
|
122
90
|
menuHeight: number = 300,
|
|
123
91
|
safeMargin: number = 10
|
|
124
92
|
): { x: number; y: number } {
|
|
@@ -186,21 +154,70 @@ export class ContextMenuUtils {
|
|
|
186
154
|
this.copiedShapes = _.cloneDeep(shapes);
|
|
187
155
|
console.log('已复制的图元:', this.copiedShapes);
|
|
188
156
|
}
|
|
189
|
-
|
|
157
|
+
|
|
190
158
|
/**
|
|
191
159
|
* 获取复制的图元
|
|
192
160
|
*/
|
|
193
161
|
static getCopiedShapes(): any[] {
|
|
194
162
|
return this.copiedShapes;
|
|
195
163
|
}
|
|
196
|
-
|
|
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
|
+
|
|
197
205
|
/**
|
|
198
206
|
* 处理复制
|
|
199
207
|
*/
|
|
200
|
-
static handleCopy(
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
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);
|
|
204
221
|
}
|
|
205
222
|
}
|
|
206
223
|
|
|
@@ -211,45 +228,46 @@ export class ContextMenuUtils {
|
|
|
211
228
|
if (this.copiedShapes.length === 0) {
|
|
212
229
|
return;
|
|
213
230
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.copiedShapes.forEach(shape => {
|
|
223
|
-
// 深拷贝图元
|
|
224
|
-
const newShape = _.cloneDeep(shape);
|
|
225
|
-
|
|
226
|
-
// 生成新的ID
|
|
227
|
-
newShape.id = getUuid();
|
|
228
|
-
|
|
229
|
-
// 更新位置到画布左上角50像素处
|
|
230
|
-
if (newShape.bounds) {
|
|
231
|
-
newShape.bounds.x = pasteX;
|
|
232
|
-
newShape.bounds.y = pasteY;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
pastedShapes.push(newShape);
|
|
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
|
|
236
238
|
});
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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);
|
|
245
252
|
}
|
|
246
253
|
|
|
247
254
|
/**
|
|
248
255
|
* 处理剪切
|
|
249
256
|
*/
|
|
250
|
-
static handleCut(
|
|
251
|
-
if (
|
|
252
|
-
|
|
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);
|
|
253
271
|
}
|
|
254
272
|
}
|
|
255
273
|
|
|
@@ -260,62 +278,3 @@ export class ContextMenuUtils {
|
|
|
260
278
|
console.log('当前选中的图元:', target);
|
|
261
279
|
}
|
|
262
280
|
}
|
|
263
|
-
|
|
264
|
-
// 默认菜单配置
|
|
265
|
-
export const defaultMenuItems: MenuItemConfig[] = [
|
|
266
|
-
{
|
|
267
|
-
id: 'property',
|
|
268
|
-
label: '属性配置',
|
|
269
|
-
icon: 'config'
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
id: 'highlight',
|
|
273
|
-
label: '树上高亮',
|
|
274
|
-
icon: 'delete'
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
id: 'locate-chart',
|
|
278
|
-
label: '所在图表',
|
|
279
|
-
icon: 'locateChart',
|
|
280
|
-
divider: true
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
id: 'new-chart',
|
|
284
|
-
label: '新建图表',
|
|
285
|
-
icon: 'diagram',
|
|
286
|
-
disabled: true
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
id: 'copy',
|
|
290
|
-
label: '复制',
|
|
291
|
-
icon: 'copy',
|
|
292
|
-
disabled: true
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
id: 'paste',
|
|
296
|
-
label: '粘贴',
|
|
297
|
-
icon: 'paste',
|
|
298
|
-
disabled: true
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
id: 'cut',
|
|
302
|
-
label: '剪切',
|
|
303
|
-
icon: 'scissors',
|
|
304
|
-
disabled: true
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
id: 'delete',
|
|
308
|
-
label: '删除',
|
|
309
|
-
icon: 'delete'
|
|
310
|
-
}
|
|
311
|
-
];
|
|
312
|
-
|
|
313
|
-
// 标准右键菜单配置
|
|
314
|
-
// export const standardContextMenuConfig: MenuConfig = {
|
|
315
|
-
// items: defaultMenuItems.map(item => {
|
|
316
|
-
// if (item.id === 'copy' || item.id === 'paste') {
|
|
317
|
-
// return { ...item, disabled: false };
|
|
318
|
-
// }
|
|
319
|
-
// return item;
|
|
320
|
-
// })
|
|
321
|
-
// };
|
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
|
-
|
|
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 =
|
|
200
|
-
while (p) { d++; p =
|
|
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 =
|
|
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,
|
package/src/utils/edgeUtils.ts
CHANGED
|
@@ -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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
if (afterPid) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
324
|
-
])
|
|
326
|
+
// eventBus.emit('shape-size-update', [
|
|
327
|
+
// { id: parent.id, bounds: afterParent },
|
|
328
|
+
// ])
|
|
325
329
|
}
|
|
326
330
|
}
|
package/src/utils/iconLoader.ts
CHANGED
|
@@ -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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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>;
|