@mx-sose-front/mx-sose-graph 1.1.7 → 1.1.9
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/assets/edgeWorker-b57ca007.js +2 -0
- package/dist/assets/edgeWorker-b57ca007.js.map +1 -0
- package/dist/index.d.ts +633 -30
- package/dist/index.esm.js +8728 -4734
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Common/Tree.vue +451 -0
- package/src/components/Common/index.ts +2 -0
- package/src/components/DiagramListTooltip/DiagramListTooltip.vue +1 -2
- package/src/components/Edge/Edge.vue +172 -169
- package/src/components/Gantt/Gantt.vue +1544 -0
- package/src/components/GanttContextMenu/GanttContextMenu.vue +304 -0
- package/src/components/InteractionLayer.vue +343 -147
- package/src/components/Matrix/Matrix.vue +828 -0
- package/src/components/Matrix/index.ts +168 -0
- package/src/components/Shape/ConceptualRole.vue +2 -34
- package/src/components/Table/Table.vue +970 -0
- package/src/constants/edgeShapeKeys.ts +8 -5
- package/src/constants/index.ts +259 -45
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useChartRowSelection.ts +456 -0
- package/src/hooks/useResize.ts +2 -2
- package/src/hooks/useVirtualScroll.ts +258 -0
- package/src/index.ts +1 -1
- package/src/render/shape-renderer.ts +62 -2
- package/src/statics/icons/childIcons//345/221/275/344/273/244@3x.png +0 -0
- package/src/statics/icons/childIcons//346/210/230/347/225/245/346/246/202/345/277/265/350/241/250@3x.png +0 -0
- package/src/statics/icons/childIcons//346/216/247/345/210/266@3x.png +0 -0
- package/src/statics/icons/createMenu/down.png +0 -0
- package/src/statics/icons/createMenu/remove.png +0 -0
- package/src/statics/icons/createMenu/up.png +0 -0
- package/src/store/graphStore.ts +217 -44
- package/src/types/index.ts +86 -4
- package/src/utils/batchAutoExpand.ts +9 -10
- package/src/utils/containers.ts +72 -17
- package/src/utils/contextMenuUtils.ts +7 -7
- package/src/utils/dateUtils.ts +160 -0
- package/src/utils/diagram.ts +10 -8
- package/src/utils/drag.ts +6 -5
- package/src/utils/edgeUtils.ts +344 -427
- package/src/utils/edgeWorker.ts +471 -0
- package/src/utils/hittest.ts +37 -38
- package/src/utils/index.ts +3 -0
- package/src/utils/keyboardUtils.ts +5 -5
- package/src/utils/packageOutline.ts +96 -0
- package/src/utils/rafThrottle.ts +162 -0
- package/src/utils/workerManager.ts +335 -0
- package/src/view/graph.vue +47 -33
- /package/src/statics/icons/childIcons//346/210/230/347/225/{245@3x.png" → 245/345/261/202@3x.png"} +0 -0
package/src/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { useGraphStore, eventBus } from './store'
|
|
|
14
14
|
export { ContextMenuUtils } from './utils/contextMenuUtils'
|
|
15
15
|
|
|
16
16
|
// 导出类型
|
|
17
|
-
export type { Shape, Bounds, Style, NameObject, TaggedValueLabel, Comparent, GraphEvents } from './types'
|
|
17
|
+
export type { Shape, Bounds, Style, NameObject, TaggedValueLabel, Comparent, GraphEvents, GanttData, GanttColumn, GanttContainsModel } from './types'
|
|
18
18
|
/**
|
|
19
19
|
* 可选的安装参数:允许使用方主动把 pinia 传进来
|
|
20
20
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CSSProperties } from "vue";
|
|
2
2
|
import type { Shape } from "@/types";
|
|
3
|
-
import { ShapeKeyMap, DiagramKeyMap, PinKeyMap, EdgeKeyMap } from "../constants";
|
|
3
|
+
import { ShapeKeyMap, DiagramKeyMap, PinKeyMap, EdgeKeyMap, GanttKeyMap, TableKeyMap, MatrixKeyMap } from "../constants";
|
|
4
4
|
import { getShapeComponentByKey } from "./shape-registry";
|
|
5
5
|
|
|
6
6
|
// 根据 shapeKey 匹配具体组件 (shapeType: 'shape')
|
|
@@ -21,6 +21,24 @@ function getComponentByEdgeKey(shapeKey: string) {
|
|
|
21
21
|
return getShapeComponentByKey(componentName) || componentName;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// 根据 shapeKey 匹配 Gantt 组件 (shapeType: 'gantt')
|
|
25
|
+
export function getComponentByGanttKey(shapeKey: string) {
|
|
26
|
+
const componentName = (GanttKeyMap as any)[shapeKey] || shapeKey;
|
|
27
|
+
return getShapeComponentByKey(componentName) || componentName;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 根据 shapeKey 匹配 Table 组件 (shapeType: 'table')
|
|
31
|
+
export function getComponentByTableKey(shapeKey: string) {
|
|
32
|
+
const componentName = (TableKeyMap as any)[shapeKey] || shapeKey;
|
|
33
|
+
return getShapeComponentByKey(componentName) || componentName;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 根据 shapeKey 匹配 Matrix 组件 (shapeType: 'matrix')
|
|
37
|
+
export function getComponentByMatrixKey(shapeKey: string) {
|
|
38
|
+
const componentName = (MatrixKeyMap as any)[shapeKey] || shapeKey;
|
|
39
|
+
return getShapeComponentByKey(componentName) || componentName;
|
|
40
|
+
}
|
|
41
|
+
|
|
24
42
|
// 根据 shapeKey 匹配 Edge 组件 (shapeType: 'diagram')
|
|
25
43
|
export function getComponentByDiagramKey(shapeKey: string) {
|
|
26
44
|
const componentName = (DiagramKeyMap as any)[shapeKey] || shapeKey;
|
|
@@ -28,6 +46,7 @@ export function getComponentByDiagramKey(shapeKey: string) {
|
|
|
28
46
|
}
|
|
29
47
|
|
|
30
48
|
// 根据 shape 读取组件
|
|
49
|
+
// 注意:后端可能对矩阵/表格/甘特图也传 shapeType='diagram',需按 shapeKey 优先匹配 Matrix/Table/Gantt
|
|
31
50
|
export const getShapeComponent = (shape: Shape) => {
|
|
32
51
|
const { shapeType, shapeKey } = shape;
|
|
33
52
|
switch (shapeType) {
|
|
@@ -36,9 +55,18 @@ export const getShapeComponent = (shape: Shape) => {
|
|
|
36
55
|
case "shape":
|
|
37
56
|
return getComponentByShapeKey(shapeKey);
|
|
38
57
|
case "diagram":
|
|
58
|
+
if ((MatrixKeyMap as any)[shapeKey]) return getComponentByMatrixKey(shapeKey);
|
|
59
|
+
if ((TableKeyMap as any)[shapeKey]) return getComponentByTableKey(shapeKey);
|
|
60
|
+
if ((GanttKeyMap as any)[shapeKey]) return getComponentByGanttKey(shapeKey);
|
|
39
61
|
return getComponentByDiagramKey(shapeKey);
|
|
40
62
|
case "edge":
|
|
41
63
|
return getComponentByEdgeKey(shapeKey);
|
|
64
|
+
case "gantt":
|
|
65
|
+
return getComponentByGanttKey(shapeKey);
|
|
66
|
+
case "table":
|
|
67
|
+
return getComponentByTableKey(shapeKey);
|
|
68
|
+
case "matrix":
|
|
69
|
+
return getComponentByMatrixKey(shapeKey);
|
|
42
70
|
default:
|
|
43
71
|
return "ShapeComponent";
|
|
44
72
|
}
|
|
@@ -108,11 +136,43 @@ export function clearEdgeStyleCache(): void {
|
|
|
108
136
|
}
|
|
109
137
|
|
|
110
138
|
// 根据 shape 的 bounds 生成样式
|
|
139
|
+
// shapeType 为 diagram 但 shapeKey 对应 Matrix/Table/Gantt 时也使用铺满样式
|
|
111
140
|
export const getShapeStyle = (shape: Shape): CSSProperties => {
|
|
112
|
-
const { bounds, shapeType } = shape
|
|
141
|
+
const { bounds, shapeType, shapeKey } = shape
|
|
113
142
|
|
|
114
143
|
if (shapeType === "edge") return getEdgeShapeStyle(shape)
|
|
115
144
|
|
|
145
|
+
const isDiagramAsMatrixOrTableOrGantt =
|
|
146
|
+
shapeType === "diagram" &&
|
|
147
|
+
((MatrixKeyMap as any)[shapeKey] || (TableKeyMap as any)[shapeKey] || (GanttKeyMap as any)[shapeKey])
|
|
148
|
+
if (isDiagramAsMatrixOrTableOrGantt) {
|
|
149
|
+
return {
|
|
150
|
+
position: "relative",
|
|
151
|
+
width: "100%",
|
|
152
|
+
height: "100%",
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (shapeType === "gantt") {
|
|
156
|
+
return {
|
|
157
|
+
position: "relative",
|
|
158
|
+
width: "100%",
|
|
159
|
+
height: "100%",
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (shapeType === "table") {
|
|
163
|
+
return {
|
|
164
|
+
position: "relative",
|
|
165
|
+
width: "100%",
|
|
166
|
+
height: "100%",
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (shapeType === "matrix") {
|
|
170
|
+
return {
|
|
171
|
+
position: "relative",
|
|
172
|
+
width: "100%",
|
|
173
|
+
height: "100%",
|
|
174
|
+
}
|
|
175
|
+
}
|
|
116
176
|
return {
|
|
117
177
|
position: "absolute",
|
|
118
178
|
left: `${bounds?.x || 0}px`,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/store/graphStore.ts
CHANGED
|
@@ -20,13 +20,57 @@ import {
|
|
|
20
20
|
syncShowComparentsByReparent,
|
|
21
21
|
} from '../utils/compartment'
|
|
22
22
|
import { EdgeUtils } from '../utils/edgeUtils'
|
|
23
|
+
import { snapPinToParentEdge } from '../utils/pinUtils'
|
|
23
24
|
import { expandParentByChild } from '../utils/autoExpandParent'
|
|
24
25
|
import { batchAutoExpandParents } from '../utils/batchAutoExpand'
|
|
25
26
|
import { adjustCanvasToFitAllShapes } from '../utils/diagram'
|
|
26
27
|
import { applyReparentAndClone, autoExpandMovedCompartmentsAfterDrag, buildDragEndPayloads, buildPrevParentMap, collectAffectedShapeIds, createOnNestDoneCallback } from '../utils/graphDragService'
|
|
27
28
|
import { createShapeOperator, type ShapeOp, type ShapeId } from "../utils/shapeOps/shapeOps"
|
|
28
29
|
type Rect = { x: number; y: number; width: number; height: number };
|
|
30
|
+
|
|
31
|
+
const GRAPH_SELECTION_STORAGE_KEY = 'mx-sose-graph:selected-state'
|
|
32
|
+
|
|
33
|
+
interface PersistedSelectionState {
|
|
34
|
+
diagramId: string | null
|
|
35
|
+
selectedIds: string[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function loadPersistedSelectionState(): PersistedSelectionState {
|
|
39
|
+
if (typeof window === 'undefined') {
|
|
40
|
+
return { diagramId: null, selectedIds: [] }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const raw = window.sessionStorage.getItem(GRAPH_SELECTION_STORAGE_KEY)
|
|
45
|
+
if (!raw) {
|
|
46
|
+
return { diagramId: null, selectedIds: [] }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const parsed = JSON.parse(raw) as Partial<PersistedSelectionState>
|
|
50
|
+
return {
|
|
51
|
+
diagramId: typeof parsed.diagramId === 'string' ? parsed.diagramId : null,
|
|
52
|
+
selectedIds: Array.isArray(parsed.selectedIds)
|
|
53
|
+
? parsed.selectedIds.filter((id): id is string => typeof id === 'string')
|
|
54
|
+
: [],
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
return { diagramId: null, selectedIds: [] }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function savePersistedSelectionState(state: PersistedSelectionState) {
|
|
62
|
+
if (typeof window === 'undefined') return
|
|
63
|
+
|
|
64
|
+
window.sessionStorage.setItem(GRAPH_SELECTION_STORAGE_KEY, JSON.stringify(state))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function clearPersistedSelectionState() {
|
|
68
|
+
if (typeof window === 'undefined') return
|
|
69
|
+
|
|
70
|
+
window.sessionStorage.removeItem(GRAPH_SELECTION_STORAGE_KEY)
|
|
71
|
+
}
|
|
29
72
|
export const useGraphStore = defineStore('graph', () => {
|
|
73
|
+
const persistedSelectionState = loadPersistedSelectionState()
|
|
30
74
|
// 状态
|
|
31
75
|
const shapes = ref<Shape[]>([])
|
|
32
76
|
// 在 store 初始化时创建一次
|
|
@@ -67,7 +111,8 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
67
111
|
//当前激活的画布id
|
|
68
112
|
const activeDiagramId = ref<string | null>(null);
|
|
69
113
|
// 当前被选中的多个 id
|
|
70
|
-
const selectedIds = ref<string[]>(
|
|
114
|
+
const selectedIds = ref<string[]>(persistedSelectionState.selectedIds)
|
|
115
|
+
const selectionDiagramId = ref<string | null>(persistedSelectionState.diagramId)
|
|
71
116
|
// 外部拖拽创建中的形状 id(用于隐藏实际形状,只显示 ghost)
|
|
72
117
|
const externalCreatingId = ref<string | null>(null)
|
|
73
118
|
// 剪切状态的图元 ID 集合(用于 SVG 遮盖层渲染)
|
|
@@ -112,6 +157,29 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
112
157
|
return map;
|
|
113
158
|
})
|
|
114
159
|
|
|
160
|
+
/**
|
|
161
|
+
* 连线索引映射
|
|
162
|
+
* 用于快速查找某个节点关联的所有连线
|
|
163
|
+
* key: 节点 ID (sourceId 或 targetId), value: 关联的 edge 数组
|
|
164
|
+
* 性能优化: 避免 updateRelatedEdges / initializeAllEdgeEndpoints 中的全量 filter + find
|
|
165
|
+
*/
|
|
166
|
+
const edgesByNodeId = computed(() => {
|
|
167
|
+
const map = new Map<string, Shape[]>()
|
|
168
|
+
shapes.value.forEach(s => {
|
|
169
|
+
if (s.shapeType === 'edge') {
|
|
170
|
+
if (s.sourceId) {
|
|
171
|
+
if (!map.has(s.sourceId)) map.set(s.sourceId, [])
|
|
172
|
+
map.get(s.sourceId)!.push(s)
|
|
173
|
+
}
|
|
174
|
+
if (s.targetId) {
|
|
175
|
+
if (!map.has(s.targetId)) map.set(s.targetId, [])
|
|
176
|
+
map.get(s.targetId)!.push(s)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
return map
|
|
181
|
+
})
|
|
182
|
+
|
|
115
183
|
//图元shapeKey
|
|
116
184
|
const taggedValueLabels = ref<string[]>([]) // 隔间组件的图元类型
|
|
117
185
|
const packagesTypes = ref<string[]>([]) // 需要展示线的隔间组件的图元类型
|
|
@@ -150,6 +218,9 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
150
218
|
// 通过事件总线发送事件
|
|
151
219
|
// eventBus.emit('shape-added', shape)
|
|
152
220
|
|
|
221
|
+
// 图元新增后调整画布大小
|
|
222
|
+
nextTick(adjustCanvasToFitAllShapes)
|
|
223
|
+
|
|
153
224
|
// 自动扩父逻辑:如果图元有父元素且需要自动扩父,触发扩父逻辑
|
|
154
225
|
const shouldAutoExpand = options?.autoExpandParent !== false // 默认为 true
|
|
155
226
|
if (shouldAutoExpand && shape.parenShapeId && shape.bounds) {
|
|
@@ -163,9 +234,19 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
163
234
|
const removeShape = (shapeId: string) => {
|
|
164
235
|
const index = shapes.value.findIndex(s => s.id == shapeId)
|
|
165
236
|
if (index > -1) {
|
|
237
|
+
const wasPrimarySelected = selectedShape.value?.id === shapeId
|
|
238
|
+
shapes.value.splice(index, 1)
|
|
239
|
+
diagrams.value = diagrams.value.filter(diagram => diagram.id !== shapeId)
|
|
240
|
+
selectedIds.value = selectedIds.value.filter(id => id !== shapeId)
|
|
241
|
+
pendingNestedIds.value = pendingNestedIds.value.filter(id => id !== shapeId)
|
|
242
|
+
if (cutShapeIds.value.has(shapeId)) {
|
|
243
|
+
const nextCutShapeIds = new Set(cutShapeIds.value)
|
|
244
|
+
nextCutShapeIds.delete(shapeId)
|
|
245
|
+
cutShapeIds.value = nextCutShapeIds
|
|
246
|
+
}
|
|
166
247
|
// 如果删除的是当前选中的形状,清除选中状态
|
|
167
|
-
|
|
168
|
-
|
|
248
|
+
syncSelectedState(wasPrimarySelected)
|
|
249
|
+
if (wasPrimarySelected && !selectedShape.value) {
|
|
169
250
|
eventBus.emit('shape-deselected')
|
|
170
251
|
}
|
|
171
252
|
}
|
|
@@ -195,7 +276,9 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
195
276
|
})
|
|
196
277
|
// 更新单个图元(不处理父子关系 / comparents,同步由外层 updateShape 负责)
|
|
197
278
|
const updateShapeRaw = (shapeId: string, updates: Partial<Shape>, id: 'id' | 'modelId' = 'id') => {
|
|
198
|
-
const shape =
|
|
279
|
+
const shape = id === 'id'
|
|
280
|
+
? shapeMap.value.get(shapeId) ?? null
|
|
281
|
+
: shapes.value.find(s => s[id] === shapeId)
|
|
199
282
|
if (shape) {
|
|
200
283
|
// 对 Pin 做强保护:永不允许把 parenShapeId 置空/undefined
|
|
201
284
|
if (shape.shapeType === 'pin' && 'parenShapeId' in updates) {
|
|
@@ -218,18 +301,23 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
218
301
|
}
|
|
219
302
|
// 更新图元数据
|
|
220
303
|
const updateShape = (shapeId: string, updates: Partial<Shape>, id: 'id' | 'modelId' = 'id') => {
|
|
221
|
-
const before =
|
|
304
|
+
const before = id === 'id'
|
|
305
|
+
? shapeMap.value.get(shapeId) ?? null
|
|
306
|
+
: shapes.value.find(s => s[id] === shapeId) ?? null
|
|
222
307
|
const oldPid = before?.parenShapeId ?? null
|
|
223
308
|
// 先真正写入
|
|
224
309
|
updateShapeRaw(shapeId, updates, id)
|
|
225
|
-
|
|
310
|
+
// updateShapeRaw 是就地修改,before 和 after 是同一个引用(id 未变时)
|
|
311
|
+
const after = before
|
|
226
312
|
if (!after) return
|
|
227
313
|
|
|
228
314
|
// 如果更新了bounds,需要重新计算相关连线的位置
|
|
315
|
+
// 在高频场景(如拖拽)中使用同步版本保证即时性,
|
|
316
|
+
// 异步版本留给 endDragShape / endResizeShape 等"结束"时刻
|
|
229
317
|
if (updates.bounds) {
|
|
230
318
|
EdgeUtils.updateRelatedEdges(shapes.value, [after.id], (shape) => {
|
|
231
319
|
updateShape(shape.id, shape);
|
|
232
|
-
});
|
|
320
|
+
}, edgesByNodeId.value, shapeMap.value);
|
|
233
321
|
}
|
|
234
322
|
|
|
235
323
|
const newPid = after.parenShapeId ?? null
|
|
@@ -259,23 +347,71 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
259
347
|
}
|
|
260
348
|
const sameId = (a?: string | null, b?: string | null) =>
|
|
261
349
|
(a ?? null) === (b ?? null)
|
|
350
|
+
const getLoadedDiagramId = () => shapes.value[0]?.diagramId ?? null
|
|
351
|
+
const persistSelectionState = () => {
|
|
352
|
+
if (!selectedIds.value.length) {
|
|
353
|
+
selectionDiagramId.value = null
|
|
354
|
+
clearPersistedSelectionState()
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const selected = selectedIds.value
|
|
359
|
+
.map(id => shapeMap.value.get(id))
|
|
360
|
+
.find((shape): shape is Shape => !!shape)
|
|
361
|
+
|
|
362
|
+
selectionDiagramId.value = selected?.diagramId ?? selectionDiagramId.value ?? getLoadedDiagramId()
|
|
363
|
+
savePersistedSelectionState({
|
|
364
|
+
diagramId: selectionDiagramId.value,
|
|
365
|
+
selectedIds: [...selectedIds.value],
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
const syncSelectedState = (emit = false) => {
|
|
369
|
+
const loadedDiagramId = getLoadedDiagramId()
|
|
370
|
+
const ids = selectionDiagramId.value && loadedDiagramId && selectionDiagramId.value !== loadedDiagramId
|
|
371
|
+
? []
|
|
372
|
+
: selectedIds.value.filter(id => shapeMap.value.has(id))
|
|
373
|
+
const nextSelectedShape = ids.length
|
|
374
|
+
? (shapeMap.value.get(ids[0]) ?? null)
|
|
375
|
+
: null
|
|
376
|
+
|
|
377
|
+
selectedIds.value = ids
|
|
378
|
+
selectedShape.value = nextSelectedShape
|
|
379
|
+
|
|
380
|
+
if (nextSelectedShape) {
|
|
381
|
+
selectionDiagramId.value = nextSelectedShape.diagramId ?? loadedDiagramId
|
|
382
|
+
persistSelectionState()
|
|
383
|
+
} else {
|
|
384
|
+
selectionDiagramId.value = null
|
|
385
|
+
clearPersistedSelectionState()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (emit) {
|
|
389
|
+
eventBus.emit('shape-selected', nextSelectedShape)
|
|
390
|
+
}
|
|
391
|
+
}
|
|
262
392
|
//选中图元
|
|
263
393
|
const selectShape = (shape: Shape | null) => {
|
|
264
394
|
const prevId = selectedShape.value?.id ?? null
|
|
265
395
|
const nextId = shape?.id ?? null
|
|
266
|
-
|
|
396
|
+
const nextSelectedIds = shape ? [shape.id] : []
|
|
397
|
+
const hasSameSelection =
|
|
398
|
+
selectedIds.value.length === nextSelectedIds.length &&
|
|
399
|
+
selectedIds.value.every((id, index) => id === nextSelectedIds[index])
|
|
400
|
+
if (sameId(prevId, nextId) && hasSameSelection) return
|
|
267
401
|
selectedShape.value = shape
|
|
268
|
-
selectedIds.value =
|
|
402
|
+
selectedIds.value = nextSelectedIds
|
|
403
|
+
selectionDiagramId.value = shape?.diagramId ?? null
|
|
404
|
+
persistSelectionState()
|
|
269
405
|
// 通过事件总线发送事件
|
|
270
406
|
eventBus.emit('shape-selected', shape)
|
|
271
407
|
}
|
|
272
408
|
// 被框选中的多个 shape/映射 selectedIds.value中的数据
|
|
273
409
|
const marqueeShapes = computed(() => {
|
|
274
|
-
const
|
|
410
|
+
const map = shapeMap.value
|
|
275
411
|
const isShape = (x: Shape | undefined): x is Shape => !!x
|
|
276
412
|
const pending = new Set(pendingNestedIds.value)
|
|
277
413
|
return selectedIds.value
|
|
278
|
-
.map(
|
|
414
|
+
.map(id => map.get(id))
|
|
279
415
|
.filter(isShape)
|
|
280
416
|
.filter(s => !pending.has(s.id))
|
|
281
417
|
})
|
|
@@ -304,18 +440,24 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
304
440
|
})
|
|
305
441
|
// 选择一组(用于框选/Shift 叠加)
|
|
306
442
|
const selectMany = (ids: string[]) => {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
443
|
+
const nextIds = Array.from(new Set(ids))
|
|
444
|
+
selectedIds.value = nextIds
|
|
445
|
+
selectedShape.value = nextIds.length
|
|
446
|
+
? (shapeMap.value.get(nextIds[0]) ?? null)
|
|
310
447
|
: null
|
|
448
|
+
selectionDiagramId.value = selectedShape.value?.diagramId ?? getLoadedDiagramId()
|
|
449
|
+
persistSelectionState()
|
|
311
450
|
}
|
|
312
451
|
// 清空选择
|
|
313
452
|
const clearSelection = () => selectShape(null)
|
|
314
453
|
// 全选图元
|
|
315
454
|
const selectAll = () => {
|
|
316
|
-
// 获取所有非diagram类型的图元
|
|
455
|
+
// 获取所有非diagram、非edge类型的图元
|
|
317
456
|
const allShapeIds = shapes.value
|
|
318
|
-
.filter(shape =>
|
|
457
|
+
.filter(shape => {
|
|
458
|
+
const t = shape.shapeType?.toLowerCase()
|
|
459
|
+
return t !== 'diagram' && t !== 'edge'
|
|
460
|
+
})
|
|
319
461
|
.map(shape => shape.id)
|
|
320
462
|
selectMany(allShapeIds)
|
|
321
463
|
}
|
|
@@ -346,6 +488,7 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
346
488
|
op,
|
|
347
489
|
})
|
|
348
490
|
pendingNestedIds.value = []
|
|
491
|
+
syncSelectedState(op === 'replace' || op === 'delete')
|
|
349
492
|
// eventBus.emit('shapes-updated', newShapes)
|
|
350
493
|
// hydrateAllComparents()
|
|
351
494
|
|
|
@@ -356,6 +499,9 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
356
499
|
batchAutoExpandParents(changedShapes, updateShape)
|
|
357
500
|
})
|
|
358
501
|
}
|
|
502
|
+
|
|
503
|
+
// 图元增删后调整画布大小(用 nextTick 合并同一 tick 内的多次调用)
|
|
504
|
+
nextTick(adjustCanvasToFitAllShapes)
|
|
359
505
|
}
|
|
360
506
|
// 获取当前图元数据
|
|
361
507
|
const getShapes = () => {
|
|
@@ -366,12 +512,15 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
366
512
|
shapes.value = []
|
|
367
513
|
diagrams.value = []
|
|
368
514
|
selectedShape.value = null
|
|
515
|
+
selectedIds.value = []
|
|
516
|
+
selectionDiagramId.value = null
|
|
369
517
|
pendingNestedIds.value = []
|
|
518
|
+
clearPersistedSelectionState()
|
|
370
519
|
eventBus.emit('shapes-cleared')
|
|
371
520
|
}
|
|
372
521
|
//拖动元素相关操作
|
|
373
522
|
// 开始拖动已有元素(仅对 shape 生效)
|
|
374
|
-
const byId = (id: string) =>
|
|
523
|
+
const byId = (id: string) => shapeMap.value.get(id) ?? null
|
|
375
524
|
const startDragShape = (shapeId: string, pointer: { x: number; y: number }) => {
|
|
376
525
|
startDrag([shapeId], pointer)
|
|
377
526
|
}
|
|
@@ -621,7 +770,7 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
621
770
|
}
|
|
622
771
|
// —— Pin 终极兜底:不允许在本次拖拽后变成“无父” ——
|
|
623
772
|
for (const id of changedIds) {
|
|
624
|
-
const node =
|
|
773
|
+
const node = shapeMap.value.get(id) ?? null
|
|
625
774
|
if (node?.shapeType === 'pin') {
|
|
626
775
|
const prevPid = prevParentById[id]
|
|
627
776
|
const nowPid = node.parenShapeId ?? null
|
|
@@ -632,9 +781,9 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
632
781
|
}
|
|
633
782
|
|
|
634
783
|
// 更新相关 edge 的 waypoints
|
|
635
|
-
EdgeUtils.
|
|
784
|
+
await EdgeUtils.updateRelatedEdgesAsync(shapes.value, changedIds, (shape) => {
|
|
636
785
|
updateShape(shape.id, shape)
|
|
637
|
-
})
|
|
786
|
+
}, edgesByNodeId.value, shapeMap.value)
|
|
638
787
|
//对“本次直接拖动的隔间”做一次自动扩容检查
|
|
639
788
|
autoExpandMovedCompartmentsAfterDrag(shapes.value, changedIds, updateShape)
|
|
640
789
|
// 把 clone 也并入“需要同步 comparents 的变更集合”
|
|
@@ -655,7 +804,7 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
655
804
|
// 在生成 payloads 之前,把父扩容先做掉
|
|
656
805
|
const shapeId = ownerPayload?.shapeId
|
|
657
806
|
if (shapeId != null) {
|
|
658
|
-
const child =
|
|
807
|
+
const child = shapeMap.value.get(shapeId) ?? null
|
|
659
808
|
if (child) {
|
|
660
809
|
expandParentByChild({
|
|
661
810
|
shapes: shapes.value,
|
|
@@ -696,16 +845,45 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
696
845
|
cleanupAfterDrag()
|
|
697
846
|
}
|
|
698
847
|
// 处理缩放结束后的事件发射
|
|
699
|
-
const endResizeShape = (id: string) => {
|
|
848
|
+
const endResizeShape = async (id: string) => {
|
|
700
849
|
// 更新相关edge的waypoints
|
|
701
|
-
EdgeUtils.
|
|
850
|
+
await EdgeUtils.updateRelatedEdgesAsync(shapes.value, [id], (shape) => {
|
|
702
851
|
updateShape(shape.id, shape);
|
|
703
|
-
});
|
|
852
|
+
}, edgesByNodeId.value, shapeMap.value);
|
|
853
|
+
|
|
854
|
+
// 重新计算子栓/端口的位置(父图元缩放后,栓需要贴合新的边缘)
|
|
855
|
+
const parentShape = shapeMap.value.get(id);
|
|
856
|
+
if (parentShape) {
|
|
857
|
+
const childIds = parentChildMap.value.get(id) ?? [];
|
|
858
|
+
for (const childId of childIds) {
|
|
859
|
+
const childShape = shapeMap.value.get(childId);
|
|
860
|
+
if (!childShape) continue;
|
|
861
|
+
if (
|
|
862
|
+
pinsTypes.value.includes(childShape.shapeKey) ||
|
|
863
|
+
portsTypes.value.includes(childShape.shapeKey)
|
|
864
|
+
) {
|
|
865
|
+
// 用栓的中心点作为参考点,重新吸附到父图元最近的边
|
|
866
|
+
const pb = childShape.bounds ?? {};
|
|
867
|
+
const pinCenterX = (pb.x ?? 0) + (pb.width ?? 0) / 2;
|
|
868
|
+
const pinCenterY = (pb.y ?? 0) + (pb.height ?? 0) / 2;
|
|
869
|
+
const newPos = snapPinToParentEdge(
|
|
870
|
+
{ x: pinCenterX, y: pinCenterY },
|
|
871
|
+
parentShape,
|
|
872
|
+
childShape
|
|
873
|
+
);
|
|
874
|
+
updateShape(childId, {
|
|
875
|
+
bounds: { ...childShape.bounds, x: newPos.x, y: newPos.y },
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
704
880
|
|
|
705
881
|
// 收集受影响的元素
|
|
706
882
|
const affected = new Set<string>();
|
|
707
883
|
affected.add(id);
|
|
708
|
-
|
|
884
|
+
// 将缩放图元的所有子孙(包括栓/端口)加入受影响集合
|
|
885
|
+
collectDescendantIds(shapes.value, id).forEach(cid => affected.add(cid));
|
|
886
|
+
const parentId = shapeMap.value.get(id)?.parenShapeId ?? null;
|
|
709
887
|
if (parentId) {
|
|
710
888
|
affected.add(parentId);
|
|
711
889
|
collectDescendantIds(shapes.value, parentId).forEach(cid => affected.add(cid));
|
|
@@ -716,8 +894,8 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
716
894
|
let cur: string | null = affectedId;
|
|
717
895
|
let guard = 0;
|
|
718
896
|
while (cur && guard++ < 1000) {
|
|
719
|
-
const s =
|
|
720
|
-
const p = s ? (s as any).parenShapeId ?? null : null;
|
|
897
|
+
const s: Shape | null = shapeMap.value.get(cur!) ?? null;
|
|
898
|
+
const p: string | null = s ? (s as any).parenShapeId ?? null : null;
|
|
721
899
|
if (!p) break;
|
|
722
900
|
affected.add(p);
|
|
723
901
|
cur = p;
|
|
@@ -726,9 +904,10 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
726
904
|
|
|
727
905
|
// 组装并派发shape-drag-end事件
|
|
728
906
|
const payloads = Array.from(affected)
|
|
729
|
-
.map(affectedId =>
|
|
907
|
+
.map(affectedId => shapeMap.value.get(affectedId))
|
|
730
908
|
.filter(Boolean)
|
|
731
909
|
.map(s => _.cloneDeep(s!)) as Shape[];
|
|
910
|
+
adjustCanvasToFitAllShapes()
|
|
732
911
|
eventBus.emit('shape-drag-end', payloads,);
|
|
733
912
|
};
|
|
734
913
|
|
|
@@ -793,10 +972,10 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
793
972
|
* 初始化所有连线的端点
|
|
794
973
|
* 用于在初始加载后端数据后,自动计算所有连线的合适端点,避免线横跨图元
|
|
795
974
|
*/
|
|
796
|
-
const initializeAllEdgeEndpoints = () => {
|
|
797
|
-
EdgeUtils.
|
|
975
|
+
const initializeAllEdgeEndpoints = async () => {
|
|
976
|
+
await EdgeUtils.initializeAllEdgeEndpointsAsync(shapes.value, (shape) => {
|
|
798
977
|
updateShape(shape.id, shape);
|
|
799
|
-
});
|
|
978
|
+
}, shapeMap.value);
|
|
800
979
|
}
|
|
801
980
|
/**
|
|
802
981
|
* 由“子元素数据”触发的扩父入口:
|
|
@@ -806,12 +985,13 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
806
985
|
*/
|
|
807
986
|
const collectAncestors = (shapes: Shape[], id: string) => {
|
|
808
987
|
const out: string[] = []
|
|
809
|
-
|
|
988
|
+
const map = shapeMap.value
|
|
989
|
+
let cur = map.get(id)
|
|
810
990
|
while (cur) {
|
|
811
991
|
out.push(cur.id)
|
|
812
992
|
const pid = cur.parenShapeId
|
|
813
993
|
if (!pid) break
|
|
814
|
-
cur =
|
|
994
|
+
cur = map.get(pid)
|
|
815
995
|
}
|
|
816
996
|
return out
|
|
817
997
|
}
|
|
@@ -827,7 +1007,7 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
827
1007
|
const before = new Map(
|
|
828
1008
|
chainIds.map(id => [
|
|
829
1009
|
id,
|
|
830
|
-
JSON.stringify(
|
|
1010
|
+
JSON.stringify(shapeMap.value.get(id)?.bounds ?? {}),
|
|
831
1011
|
]),
|
|
832
1012
|
)
|
|
833
1013
|
|
|
@@ -840,7 +1020,7 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
840
1020
|
|
|
841
1021
|
const payload: ShapeSizeUpdatePayload[] = chainIds
|
|
842
1022
|
.filter(id => id !== child.id)
|
|
843
|
-
.map(id =>
|
|
1023
|
+
.map(id => shapeMap.value.get(id))
|
|
844
1024
|
.filter((s): s is Shape => !!s)
|
|
845
1025
|
.filter(s => before.get(s.id) !== JSON.stringify(s.bounds ?? {})) // diff
|
|
846
1026
|
.map(s => ({ id: s.id, bounds: JSON.stringify(s.bounds ?? {}) }))
|
|
@@ -905,6 +1085,7 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
905
1085
|
hasSelectedShape,
|
|
906
1086
|
parentChildMap, // 父子关系索引映射
|
|
907
1087
|
shapeMap, // 图元ID索引映射 (性能优化)
|
|
1088
|
+
edgesByNodeId, // 连线索引映射 (性能优化)
|
|
908
1089
|
marqueeShapes,
|
|
909
1090
|
ghostShadow,
|
|
910
1091
|
scales,
|
|
@@ -950,12 +1131,4 @@ export const useGraphStore = defineStore('graph', () => {
|
|
|
950
1131
|
clearCutShapeIds,
|
|
951
1132
|
setCopiedShapesCount,
|
|
952
1133
|
}
|
|
953
|
-
}
|
|
954
|
-
//配置持久化
|
|
955
|
-
{
|
|
956
|
-
//@ts-ignore
|
|
957
|
-
persist: {
|
|
958
|
-
paths: ['shapes', 'selectedShape']
|
|
959
|
-
} as any,
|
|
960
|
-
}
|
|
961
|
-
)
|
|
1134
|
+
})
|