@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
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { ref, type Ref } from 'vue';
|
|
1
|
+
import { ref, onUnmounted, type Ref } from 'vue';
|
|
2
2
|
import type { Shape, Rect } from '../types';
|
|
3
3
|
import { useGraphStore } from '../store/graphStore';
|
|
4
|
-
import { toLocalPoint, ghostResizeStep } from '
|
|
5
|
-
import { calculateTextMinWidth } from '
|
|
6
|
-
import { getPolicy } from '
|
|
7
|
-
import { withDrag } from '
|
|
8
|
-
import { isCompartment, clampCompartmentResize } from '
|
|
9
|
-
import { clampParentRectToChildrenGap } from '
|
|
10
|
-
import { finalizeAfterTransform } from '
|
|
11
|
-
import { normalizeZOrder } from '
|
|
4
|
+
import { toLocalPoint, ghostResizeStep } from '../utils/geom';
|
|
5
|
+
import { calculateTextMinWidth } from '../utils/diagram';
|
|
6
|
+
import { getPolicy } from '../utils/policy';
|
|
7
|
+
import { withDrag } from '../utils/dom';
|
|
8
|
+
import { isCompartment, clampCompartmentResize } from '../utils/compartment';
|
|
9
|
+
import { clampParentRectToChildrenGap } from '../utils/containers';
|
|
10
|
+
import { finalizeAfterTransform } from '../utils/drag';
|
|
11
|
+
import { normalizeZOrder } from '../utils/zorder';
|
|
12
12
|
import { eventBus } from '../store';
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -19,8 +19,6 @@ const minH = 30;
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* 最小尺寸配置接口
|
|
22
|
-
* @property minW - 最小宽度
|
|
23
|
-
* @property minH - 最小高度
|
|
24
22
|
*/
|
|
25
23
|
export interface MinDimensions {
|
|
26
24
|
minW: number;
|
|
@@ -29,35 +27,16 @@ export interface MinDimensions {
|
|
|
29
27
|
|
|
30
28
|
/**
|
|
31
29
|
* 最小尺寸计算模式
|
|
32
|
-
* @property RESIZE - 缩放过程中的最小尺寸
|
|
33
|
-
* @property STOP - 缩放结束时的最小尺寸
|
|
34
30
|
*/
|
|
35
31
|
export enum MinDimensionMode {
|
|
36
32
|
RESIZE = 'resize',
|
|
37
33
|
STOP = 'stop'
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
/**
|
|
41
|
-
* 缩放状态接口
|
|
42
|
-
* 包含缩放过程中需要追踪的所有响应式状态
|
|
43
|
-
*/
|
|
44
|
-
export interface ResizeState {
|
|
45
|
-
isResizing: Ref<boolean>; // 是否正在缩放
|
|
46
|
-
resizeDirection: Ref<string>; // 当前缩放方向 (nw/ne/sw/se)
|
|
47
|
-
startPos: Ref<{ x: number; y: number }>; // 缩放起始鼠标位置
|
|
48
|
-
startBounds: Ref<Rect>; // 缩放起始时的元素边界
|
|
49
|
-
resizingTarget: Ref<Shape | null>; // 当前缩放的目标元素
|
|
50
|
-
groupBase: Ref<Record<string, Rect>>; // 缩放前的基础边界(用于多选)
|
|
51
|
-
groupGhost: Ref<Record<string, Rect>>; // 缩放预览边界(用于多选)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
36
|
/**
|
|
55
37
|
* 缩放配置接口
|
|
56
|
-
* @property packages - 需要特殊最小高度处理的 shapeKey 列表
|
|
57
|
-
* @property diagram - diagram 组件的 shapeKey 列表
|
|
58
|
-
* @property taggedValueLabels - taggedValueLabels 组件的 shapeKey 列表
|
|
59
38
|
*/
|
|
60
|
-
export interface
|
|
39
|
+
export interface UseResizeConfig {
|
|
61
40
|
packages?: string[];
|
|
62
41
|
diagram?: string[];
|
|
63
42
|
taggedValueLabels?: string[];
|
|
@@ -66,54 +45,58 @@ export interface ResizeConfig {
|
|
|
66
45
|
/**
|
|
67
46
|
* 缩放回调函数接口
|
|
68
47
|
*/
|
|
69
|
-
export interface
|
|
70
|
-
onResizeStart?: (target: Shape) => void;
|
|
71
|
-
onResizeEnd?: (target: Shape) => void;
|
|
72
|
-
onShapeUpdate?: (id: string, updates: { bounds: Rect }) => void;
|
|
48
|
+
export interface UseResizeCallbacks {
|
|
49
|
+
onResizeStart?: (target: Shape) => void;
|
|
50
|
+
onResizeEnd?: (target: Shape) => void;
|
|
51
|
+
onShapeUpdate?: (id: string, updates: { bounds: Rect }) => void;
|
|
73
52
|
}
|
|
74
53
|
|
|
75
54
|
/**
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
55
|
+
* useResize 返回类型
|
|
56
|
+
*/
|
|
57
|
+
export interface UseResizeReturn {
|
|
58
|
+
isResizing: Ref<boolean>;
|
|
59
|
+
resizeDirection: Ref<string>;
|
|
60
|
+
startPos: Ref<{ x: number; y: number }>;
|
|
61
|
+
startBounds: Ref<Rect>;
|
|
62
|
+
resizingTarget: Ref<Shape | null>;
|
|
63
|
+
groupBase: Ref<Record<string, Rect>>;
|
|
64
|
+
groupGhost: Ref<Record<string, Rect>>;
|
|
65
|
+
startResize: (e: MouseEvent, dir: "nw" | "ne" | "sw" | "se", target: Shape) => void;
|
|
66
|
+
handleResize: (e: MouseEvent) => void;
|
|
67
|
+
stopResize: () => void;
|
|
68
|
+
cancelResize: () => void;
|
|
69
|
+
getMinDimensions: (shape: Shape, baseMinW?: number, mode?: MinDimensionMode) => MinDimensions;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 图元缩放 Composable
|
|
82
74
|
*
|
|
83
75
|
* @param layerRef - 交互层的 DOM 引用
|
|
84
|
-
* @param config -
|
|
76
|
+
* @param config - 缩放配置
|
|
85
77
|
* @param callbacks - 缩放过程的回调函数
|
|
86
|
-
* @returns
|
|
78
|
+
* @returns 缩放相关的状态和方法
|
|
87
79
|
*/
|
|
88
|
-
export function
|
|
89
|
-
layerRef:
|
|
90
|
-
config:
|
|
91
|
-
callbacks:
|
|
92
|
-
) {
|
|
80
|
+
export function useResize(
|
|
81
|
+
layerRef: Ref<HTMLDivElement | null>,
|
|
82
|
+
config: UseResizeConfig,
|
|
83
|
+
callbacks: UseResizeCallbacks = {}
|
|
84
|
+
): UseResizeReturn {
|
|
93
85
|
const graphStore = useGraphStore();
|
|
94
86
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const groupGhost = ref<Record<string, Rect>>({}); // 多选时的预览边界
|
|
87
|
+
// 缩放状态
|
|
88
|
+
const isResizing = ref(false);
|
|
89
|
+
const resizeDirection = ref<"nw" | "ne" | "sw" | "se" | "">("");
|
|
90
|
+
const startPos = ref({ x: 0, y: 0 });
|
|
91
|
+
const startBounds = ref<Rect>({ x: 0, y: 0, width: 0, height: 0 });
|
|
92
|
+
const resizingTarget = ref<Shape | null>(null);
|
|
93
|
+
const groupBase = ref<Record<string, Rect>>({});
|
|
94
|
+
const groupGhost = ref<Record<string, Rect>>({});
|
|
104
95
|
|
|
105
|
-
let offDrag: (() => void) | null = null;
|
|
96
|
+
let offDrag: (() => void) | null = null;
|
|
106
97
|
|
|
107
98
|
/**
|
|
108
99
|
* 获取指定元素的最小尺寸限制
|
|
109
|
-
*
|
|
110
|
-
* 根据元素的 shapeKey 类型返回不同的最小高度和宽度限制
|
|
111
|
-
* 不同类型的组件有不同的最小尺寸要求
|
|
112
|
-
*
|
|
113
|
-
* @param shape - 要计算最小尺寸的元素
|
|
114
|
-
* @param baseMinW - 基础最小宽度(默认 50)
|
|
115
|
-
* @param mode - 计算模式(RESIZE 或 STOP)
|
|
116
|
-
* @returns 最小尺寸配置 { minW, minH }
|
|
117
100
|
*/
|
|
118
101
|
const getMinDimensions = (
|
|
119
102
|
shape: Shape,
|
|
@@ -123,24 +106,19 @@ export function createResizeUtils(
|
|
|
123
106
|
let result: MinDimensions = { minW: baseMinW, minH: 60 };
|
|
124
107
|
|
|
125
108
|
if (shape.shapeKey) {
|
|
126
|
-
// packages 类型:缩放时最小高度 75,结束时 90
|
|
127
109
|
if (config.packages && config.packages.includes(shape.shapeKey)) {
|
|
128
110
|
result.minH = mode === MinDimensionMode.RESIZE ? 75 : 90;
|
|
129
111
|
}
|
|
130
|
-
// diagram 类型:缩放时最小高度 80,结束时 50
|
|
131
112
|
else if (config.diagram && config.diagram.includes(shape.shapeKey)) {
|
|
132
113
|
result.minH = mode === MinDimensionMode.RESIZE ? 80 : 50;
|
|
133
114
|
}
|
|
134
|
-
// taggedValueLabels 类型:最小高度 125
|
|
135
115
|
else if (config.taggedValueLabels && config.taggedValueLabels.includes(shape.shapeKey)) {
|
|
136
116
|
result.minH = 125;
|
|
137
117
|
}
|
|
138
|
-
// pin 类型:最小宽高 22
|
|
139
118
|
else if (graphStore.pinsTypes.includes(shape.shapeKey)) {
|
|
140
119
|
result.minH = 22;
|
|
141
120
|
result.minW = 22;
|
|
142
121
|
}
|
|
143
|
-
// port 类型:最小宽高 22
|
|
144
122
|
else if (graphStore.portsTypes.includes(shape.shapeKey)) {
|
|
145
123
|
result.minH = 22;
|
|
146
124
|
result.minW = 22;
|
|
@@ -152,57 +130,36 @@ export function createResizeUtils(
|
|
|
152
130
|
|
|
153
131
|
/**
|
|
154
132
|
* 开始缩放
|
|
155
|
-
*
|
|
156
|
-
* 触发时机:用户按下缩放手柄时
|
|
157
|
-
*
|
|
158
|
-
* 执行步骤:
|
|
159
|
-
* 1. 检查是否允许缩放(ConceptRole 或多选时不允)
|
|
160
|
-
* 2. 设置缩放状态(isResizing = true)
|
|
161
|
-
* 3. 记录缩放方向和目标元素
|
|
162
|
-
* 4. 记录起始位置和元素边界
|
|
163
|
-
* 5. 初始化 groupBase 和 groupGhost
|
|
164
|
-
* 6. 绑定拖拽事件监听(mousemove 和 mouseup)
|
|
165
|
-
*
|
|
166
|
-
* @param e - 鼠标事件
|
|
167
|
-
* @param dir - 缩放方向 (nw/ne/sw/se)
|
|
168
|
-
* @param target - 目标元素
|
|
169
133
|
*/
|
|
170
134
|
const startResize = (
|
|
171
135
|
e: MouseEvent,
|
|
172
136
|
dir: "nw" | "ne" | "sw" | "se",
|
|
173
137
|
target: Shape
|
|
174
138
|
) => {
|
|
175
|
-
// ConceptRole 类型不允许缩放
|
|
176
139
|
if (target.shapeKey === 'ConceptRole') {
|
|
177
140
|
e.preventDefault();
|
|
178
141
|
e.stopPropagation();
|
|
179
142
|
return;
|
|
180
143
|
}
|
|
181
144
|
|
|
182
|
-
// 多选状态下不允许缩放
|
|
183
145
|
if (graphStore.selectedIds.length > 1) {
|
|
184
146
|
e.preventDefault();
|
|
185
147
|
e.stopPropagation();
|
|
186
148
|
return;
|
|
187
149
|
}
|
|
188
150
|
|
|
189
|
-
// 阻止默认行为和事件冒泡
|
|
190
151
|
e.preventDefault();
|
|
191
152
|
e.stopPropagation();
|
|
192
153
|
|
|
193
|
-
// 设置缩放状态
|
|
194
154
|
isResizing.value = true;
|
|
195
155
|
resizingTarget.value = target;
|
|
196
156
|
resizeDirection.value = dir;
|
|
197
157
|
|
|
198
|
-
// 触发开始回调
|
|
199
158
|
callbacks.onResizeStart?.(target);
|
|
200
159
|
|
|
201
|
-
// 记录起始位置
|
|
202
160
|
const anchor = toLocalPoint(e, layerRef.value!);
|
|
203
161
|
startPos.value = { x: anchor.x, y: anchor.y };
|
|
204
162
|
|
|
205
|
-
// 记录起始边界
|
|
206
163
|
const b = target.bounds ?? {};
|
|
207
164
|
startBounds.value = {
|
|
208
165
|
x: b.x ?? 0,
|
|
@@ -211,26 +168,14 @@ export function createResizeUtils(
|
|
|
211
168
|
height: b.height ?? 50,
|
|
212
169
|
};
|
|
213
170
|
|
|
214
|
-
// 初始化预览状态
|
|
215
171
|
groupBase.value = { [target.id]: { ...startBounds.value } };
|
|
216
172
|
groupGhost.value = { [target.id]: { ...startBounds.value } };
|
|
217
173
|
|
|
218
|
-
// 绑定拖拽事件
|
|
219
174
|
offDrag = withDrag(handleResize, stopResize);
|
|
220
175
|
};
|
|
221
176
|
|
|
222
177
|
/**
|
|
223
178
|
* 缩放进行中
|
|
224
|
-
*
|
|
225
|
-
* 触发时机:用户拖拽缩放手柄时(由 withDrag 调用)
|
|
226
|
-
*
|
|
227
|
-
* 执行步骤:
|
|
228
|
-
* 1. 获取当前鼠标位置
|
|
229
|
-
* 2. 计算新的边界(基于起始边界和鼠标偏移)
|
|
230
|
-
* 3. 应用各类约束(容器边界、文本最小宽度、隔间组件、父子间隙)
|
|
231
|
-
* 4. 更新 groupGhost 供 UI 显示预览
|
|
232
|
-
*
|
|
233
|
-
* @param e - 鼠标事件
|
|
234
179
|
*/
|
|
235
180
|
const handleResize = (e: MouseEvent) => {
|
|
236
181
|
if (!isResizing.value || !resizingTarget.value) return;
|
|
@@ -240,7 +185,6 @@ export function createResizeUtils(
|
|
|
240
185
|
const handle = resizeDirection.value as "nw" | "ne" | "sw" | "se";
|
|
241
186
|
const shape = resizingTarget.value;
|
|
242
187
|
|
|
243
|
-
// 容器边界约束(只限制左上角不小于 0,不限制右下角以允许无限放大)
|
|
244
188
|
const container = {
|
|
245
189
|
left: 0,
|
|
246
190
|
top: 0,
|
|
@@ -248,43 +192,32 @@ export function createResizeUtils(
|
|
|
248
192
|
bottom: Infinity,
|
|
249
193
|
};
|
|
250
194
|
|
|
251
|
-
// 获取元素的边界约束策略
|
|
252
195
|
const edges = getPolicy(shape).constrainToDiagram;
|
|
253
|
-
|
|
254
|
-
// 计算基于文本内容的最小宽度
|
|
255
196
|
const dynamicMinW = calculateTextMinWidth(shape);
|
|
256
197
|
|
|
257
|
-
// 判断是放大还是缩小
|
|
258
198
|
const dx = curr.x - startPos.value.x;
|
|
259
199
|
const isEnlarging = (handle === 'ne' || handle === 'se') ? dx > 0 : dx < 0;
|
|
260
|
-
// 放大时使用较小约束,缩小时不小于文本所需宽度
|
|
261
200
|
const baseMinW = isEnlarging ? minW / 2 : Math.max(minW, dynamicMinW);
|
|
262
201
|
|
|
263
|
-
// 获取该类型的最小尺寸限制
|
|
264
202
|
const { minW: effectiveMinW, minH: effectiveMinH } = getMinDimensions(shape, baseMinW);
|
|
265
203
|
|
|
266
|
-
// 判断是否为 diagram 组件
|
|
267
204
|
const isDiagramComponent = shape.shapeKey && config.diagram && config.diagram.includes(shape.shapeKey);
|
|
268
205
|
|
|
269
|
-
// 计算预览边界
|
|
270
206
|
let next = ghostResizeStep(
|
|
271
207
|
{ x: base.x, y: base.y, width: base.width, height: base.height },
|
|
272
208
|
handle,
|
|
273
209
|
{ x: startPos.value.x, y: startPos.value.y },
|
|
274
210
|
curr,
|
|
275
211
|
container,
|
|
276
|
-
// 只限制 left 和 top
|
|
277
212
|
{ left: edges.left, top: edges.top, right: false, bottom: false },
|
|
278
213
|
effectiveMinW,
|
|
279
214
|
effectiveMinH
|
|
280
215
|
);
|
|
281
216
|
|
|
282
|
-
// diagram 组件保持高度不变
|
|
283
217
|
if (isDiagramComponent && shape.shapeType === 'shape') {
|
|
284
218
|
next = { ...next, height: base.height };
|
|
285
219
|
}
|
|
286
220
|
|
|
287
|
-
// 画布类型(diagram)不移动位置
|
|
288
221
|
const isDiagram = (shape as any).shapeType?.toLowerCase?.() === "diagram";
|
|
289
222
|
if (isDiagram) {
|
|
290
223
|
next = { ...next, x: base.x, y: base.y };
|
|
@@ -292,7 +225,6 @@ export function createResizeUtils(
|
|
|
292
225
|
next.y = Math.max(0, next.y);
|
|
293
226
|
}
|
|
294
227
|
|
|
295
|
-
// 隔间组件有特殊的缩放约束
|
|
296
228
|
if (isCompartment(shape)) {
|
|
297
229
|
next = clampCompartmentResize(
|
|
298
230
|
graphStore.shapes,
|
|
@@ -302,7 +234,6 @@ export function createResizeUtils(
|
|
|
302
234
|
);
|
|
303
235
|
}
|
|
304
236
|
|
|
305
|
-
// 确保父元素缩放后子元素仍然可见(有间隙约束)
|
|
306
237
|
next = clampParentRectToChildrenGap(
|
|
307
238
|
graphStore.shapes,
|
|
308
239
|
shape,
|
|
@@ -314,7 +245,6 @@ export function createResizeUtils(
|
|
|
314
245
|
groupGhost.value
|
|
315
246
|
);
|
|
316
247
|
|
|
317
|
-
// 更新预览边界
|
|
318
248
|
groupGhost.value = {
|
|
319
249
|
...groupGhost.value,
|
|
320
250
|
[shape.id]: next,
|
|
@@ -323,17 +253,6 @@ export function createResizeUtils(
|
|
|
323
253
|
|
|
324
254
|
/**
|
|
325
255
|
* 停止缩放
|
|
326
|
-
*
|
|
327
|
-
* 触发时机:用户松开鼠标(由 withDrag 调用)
|
|
328
|
-
*
|
|
329
|
-
* 执行步骤:
|
|
330
|
-
* 1. 获取最终边界(groupGhost 或起始边界)
|
|
331
|
-
* 2. 应用最小尺寸约束
|
|
332
|
-
* 3. 更新元素实际边界
|
|
333
|
-
* 4. 处理可能的重父子关系
|
|
334
|
-
* 5. 清理缩放状态
|
|
335
|
-
*
|
|
336
|
-
* @param e - 鼠标事件(未使用,但 withDrag 要求)
|
|
337
256
|
*/
|
|
338
257
|
const stopResize = () => {
|
|
339
258
|
if (!isResizing.value || !resizingTarget.value) return;
|
|
@@ -342,13 +261,11 @@ export function createResizeUtils(
|
|
|
342
261
|
const shape = resizingTarget.value;
|
|
343
262
|
const final = groupGhost.value[id] ?? startBounds.value;
|
|
344
263
|
|
|
345
|
-
// 计算最小尺寸约束
|
|
346
264
|
const dynamicMinW = calculateTextMinWidth(shape);
|
|
347
265
|
const { minW: effectiveMinW, minH: minHeight } = getMinDimensions(shape, dynamicMinW, MinDimensionMode.STOP);
|
|
348
266
|
|
|
349
267
|
const isDiagramComponent = shape.shapeKey && config.diagram && config.diagram.includes(shape.shapeKey);
|
|
350
268
|
|
|
351
|
-
// 应用最小尺寸约束
|
|
352
269
|
let finalBounds = { ...final };
|
|
353
270
|
if (final.width < effectiveMinW) {
|
|
354
271
|
finalBounds.width = effectiveMinW;
|
|
@@ -360,21 +277,17 @@ export function createResizeUtils(
|
|
|
360
277
|
finalBounds = { ...finalBounds, height: startBounds.value.height };
|
|
361
278
|
}
|
|
362
279
|
|
|
363
|
-
// 检查边界是否有变化
|
|
364
280
|
const shapeBefore = graphStore.shapes.find((s) => s.id === id);
|
|
365
281
|
const changed = shapeBefore && JSON.stringify(shapeBefore.bounds) !== JSON.stringify(finalBounds);
|
|
366
282
|
|
|
367
283
|
if (changed) {
|
|
368
|
-
// 更新元素边界
|
|
369
284
|
callbacks.onShapeUpdate?.(id, { bounds: finalBounds });
|
|
370
285
|
graphStore.updateShape(id, { bounds: finalBounds });
|
|
371
286
|
|
|
372
|
-
// 同步更新 selectedShape
|
|
373
287
|
if (graphStore.selectedShape?.id === id && graphStore.selectedShape) {
|
|
374
288
|
graphStore.selectedShape.bounds = { ...finalBounds };
|
|
375
289
|
}
|
|
376
290
|
|
|
377
|
-
// 处理重父子关系
|
|
378
291
|
const containerForFinalize =
|
|
379
292
|
graphStore.hoverContainerId && graphStore.hoverNestable ? graphStore.hoverContainerId : null;
|
|
380
293
|
|
|
@@ -388,7 +301,6 @@ export function createResizeUtils(
|
|
|
388
301
|
graphStore.updateShape
|
|
389
302
|
);
|
|
390
303
|
|
|
391
|
-
// 如果有重父子,需要重新计算 z-order
|
|
392
304
|
if (didReparent) {
|
|
393
305
|
normalizeZOrder(
|
|
394
306
|
graphStore.shapes,
|
|
@@ -399,15 +311,12 @@ export function createResizeUtils(
|
|
|
399
311
|
);
|
|
400
312
|
}
|
|
401
313
|
|
|
402
|
-
// 通知 store 缩放结束
|
|
403
314
|
graphStore.endResizeShape(id);
|
|
404
315
|
}
|
|
405
316
|
|
|
406
|
-
// 触发结束回调
|
|
407
317
|
callbacks.onResizeEnd?.(resizingTarget.value);
|
|
408
318
|
eventBus.emit('resize-end', { target: resizingTarget.value });
|
|
409
319
|
|
|
410
|
-
// 清理状态
|
|
411
320
|
isResizing.value = false;
|
|
412
321
|
resizeDirection.value = "";
|
|
413
322
|
resizingTarget.value = null;
|
|
@@ -419,12 +328,6 @@ export function createResizeUtils(
|
|
|
419
328
|
|
|
420
329
|
/**
|
|
421
330
|
* 取消缩放
|
|
422
|
-
*
|
|
423
|
-
* 用于外部强制取消缩放操作(如切换选中元素)
|
|
424
|
-
*
|
|
425
|
-
* 执行步骤:
|
|
426
|
-
* 1. 清理拖拽事件监听
|
|
427
|
-
* 2. 重置所有缩放状态
|
|
428
331
|
*/
|
|
429
332
|
const cancelResize = () => {
|
|
430
333
|
if (offDrag) {
|
|
@@ -438,14 +341,11 @@ export function createResizeUtils(
|
|
|
438
341
|
groupGhost.value = {};
|
|
439
342
|
};
|
|
440
343
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
* - handleResize/stopResize/cancelResize:内部使用
|
|
447
|
-
* - getMinDimensions:供外部计算最小尺寸
|
|
448
|
-
*/
|
|
344
|
+
// 组件卸载时自动清理
|
|
345
|
+
onUnmounted(() => {
|
|
346
|
+
cancelResize();
|
|
347
|
+
});
|
|
348
|
+
|
|
449
349
|
return {
|
|
450
350
|
isResizing,
|
|
451
351
|
resizeDirection,
|
|
@@ -458,6 +358,6 @@ export function createResizeUtils(
|
|
|
458
358
|
handleResize,
|
|
459
359
|
stopResize,
|
|
460
360
|
cancelResize,
|
|
461
|
-
getMinDimensions
|
|
361
|
+
getMinDimensions,
|
|
462
362
|
};
|
|
463
363
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,9 @@ export { GraphView }
|
|
|
10
10
|
// 导出 store
|
|
11
11
|
export { useGraphStore, eventBus } from './store'
|
|
12
12
|
|
|
13
|
+
// 导出工具类
|
|
14
|
+
export { ContextMenuUtils } from './utils/contextMenuUtils'
|
|
15
|
+
|
|
13
16
|
// 导出类型
|
|
14
17
|
export type { Shape, Bounds, Style, NameObject, TaggedValueLabel, Comparent, GraphEvents } from './types'
|
|
15
18
|
/**
|
|
@@ -35,7 +38,7 @@ export const MxSoseGraphPlugin: Plugin<[MxSoseGraphOptions?]> = {
|
|
|
35
38
|
if (import.meta.env.DEV) {
|
|
36
39
|
console.warn(
|
|
37
40
|
'[mx-sose-graph] 未找到 pinia 实例,请确保:' +
|
|
38
|
-
|
|
41
|
+
'1)先 app.use(pinia),2)再 app.use(MxSoseGraphPlugin, { pinia })'
|
|
39
42
|
)
|
|
40
43
|
}
|
|
41
44
|
}
|
|
@@ -36,68 +36,81 @@ export const getShapeComponent = (shape: Shape) => {
|
|
|
36
36
|
return "ShapeComponent";
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// Edge waypoints 解析结果缓存,避免每帧重复 JSON.parse(约 60+ 图元时明显)
|
|
40
|
+
const EDGE_STYLE_CACHE_MAX = 128
|
|
41
|
+
const edgeStyleCache = new Map<string, CSSProperties>()
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
function getEdgeShapeStyle(shape: Shape): CSSProperties {
|
|
44
|
+
const raw = shape.waypointId
|
|
45
|
+
const cacheKey = shape.id + (typeof raw === "string" ? raw : raw ? JSON.stringify(raw) : "")
|
|
46
|
+
const cached = edgeStyleCache.get(cacheKey)
|
|
47
|
+
if (cached) return cached
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
} else if (Array.isArray(shape.waypointId)) {
|
|
57
|
-
waypoints = shape.waypointId;
|
|
49
|
+
let waypoints: Array<{ x: number; y: number }> = []
|
|
50
|
+
if (raw) {
|
|
51
|
+
if (typeof raw === "string") {
|
|
52
|
+
try {
|
|
53
|
+
waypoints = JSON.parse(raw)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error("解析waypointId失败:", error)
|
|
58
56
|
}
|
|
57
|
+
} else if (Array.isArray(raw)) {
|
|
58
|
+
waypoints = raw
|
|
59
59
|
}
|
|
60
|
+
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
let result: CSSProperties
|
|
63
|
+
if (waypoints.length === 0) {
|
|
64
|
+
result = {
|
|
65
|
+
position: "absolute",
|
|
66
|
+
left: "0px",
|
|
67
|
+
top: "0px",
|
|
68
|
+
width: "100px",
|
|
69
|
+
height: "50px",
|
|
70
|
+
opacity: 0,
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const padding = 10;
|
|
83
|
-
const width = Math.max(maxX - minX + padding * 2, 20); // 最小宽度20
|
|
84
|
-
const height = maxY - minY + padding * 2;
|
|
85
|
-
|
|
86
|
-
return {
|
|
72
|
+
} else {
|
|
73
|
+
const xs = waypoints.map((wp) => wp.x)
|
|
74
|
+
const ys = waypoints.map((wp) => wp.y)
|
|
75
|
+
const minX = Math.min(...xs)
|
|
76
|
+
const maxX = Math.max(...xs)
|
|
77
|
+
const minY = Math.min(...ys)
|
|
78
|
+
const maxY = Math.max(...ys)
|
|
79
|
+
const padding = 10
|
|
80
|
+
const width = Math.max(maxX - minX + padding * 2, 20)
|
|
81
|
+
const height = maxY - minY + padding * 2
|
|
82
|
+
result = {
|
|
87
83
|
position: "absolute",
|
|
88
84
|
left: `${minX - padding}px`,
|
|
89
85
|
top: `${minY - padding}px`,
|
|
90
86
|
width: `${width}px`,
|
|
91
87
|
height: `${height}px`,
|
|
92
|
-
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (edgeStyleCache.size >= EDGE_STYLE_CACHE_MAX) {
|
|
91
|
+
const firstKey = edgeStyleCache.keys().next().value
|
|
92
|
+
if (firstKey != null) edgeStyleCache.delete(firstKey)
|
|
93
93
|
}
|
|
94
|
+
edgeStyleCache.set(cacheKey, result)
|
|
95
|
+
return result
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 批量更新图元时调用,避免缓存无限增长 */
|
|
99
|
+
export function clearEdgeStyleCache(): void {
|
|
100
|
+
edgeStyleCache.clear()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 根据 shape 的 bounds 生成样式
|
|
104
|
+
export const getShapeStyle = (shape: Shape): CSSProperties => {
|
|
105
|
+
const { bounds, shapeType } = shape
|
|
106
|
+
|
|
107
|
+
if (shapeType === "edge") return getEdgeShapeStyle(shape)
|
|
94
108
|
|
|
95
|
-
// 其他组件使用bounds
|
|
96
109
|
return {
|
|
97
110
|
position: "absolute",
|
|
98
111
|
left: `${bounds?.x || 0}px`,
|
|
99
112
|
top: `${bounds?.y || 0}px`,
|
|
100
113
|
width: `${bounds?.width || 100}px`,
|
|
101
114
|
height: `${bounds?.height || 50}px`,
|
|
102
|
-
}
|
|
103
|
-
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|