@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/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,45 +1,15 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
181
|
-
if (
|
|
182
|
-
|
|
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 (
|
|
191
|
-
|
|
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(
|
|
199
|
-
if (
|
|
200
|
-
|
|
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
|
-
};
|
package/src/utils/diagram.ts
CHANGED
|
@@ -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
|
-
|
|
379
|
-
const
|
|
380
|
-
const
|
|
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 ((
|
|
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 = (
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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 =
|
|
417
|
-
const 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
|
-
|
|
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>;
|