@logicflow/extension 2.2.0-alpha.6 → 2.2.0-alpha.7
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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +6 -0
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/es/bpmn-elements-adapter/json2xml.d.ts +2 -1
- package/es/bpmn-elements-adapter/json2xml.js +18 -4
- package/es/bpmn-elements-adapter/xml2json.js +2 -7
- package/es/index.d.ts +1 -0
- package/es/index.js +2 -0
- package/es/pool/LaneModel.d.ts +90 -0
- package/es/pool/LaneModel.js +252 -0
- package/es/pool/LaneView.d.ts +40 -0
- package/es/pool/LaneView.js +202 -0
- package/es/pool/PoolModel.d.ts +120 -0
- package/es/pool/PoolModel.js +586 -0
- package/es/pool/PoolView.d.ts +17 -0
- package/es/pool/PoolView.js +76 -0
- package/es/pool/constant.d.ts +15 -0
- package/es/pool/constant.js +17 -0
- package/es/pool/index.d.ts +89 -0
- package/es/pool/index.js +524 -0
- package/es/pool/utils.d.ts +19 -0
- package/es/pool/utils.js +33 -0
- package/lib/bpmn-elements-adapter/json2xml.d.ts +2 -1
- package/lib/bpmn-elements-adapter/json2xml.js +19 -4
- package/lib/bpmn-elements-adapter/xml2json.js +2 -7
- package/lib/index.d.ts +1 -0
- package/lib/index.js +2 -0
- package/lib/pool/LaneModel.d.ts +90 -0
- package/lib/pool/LaneModel.js +255 -0
- package/lib/pool/LaneView.d.ts +40 -0
- package/lib/pool/LaneView.js +205 -0
- package/lib/pool/PoolModel.d.ts +120 -0
- package/lib/pool/PoolModel.js +589 -0
- package/lib/pool/PoolView.d.ts +17 -0
- package/lib/pool/PoolView.js +79 -0
- package/lib/pool/constant.d.ts +15 -0
- package/lib/pool/constant.js +20 -0
- package/lib/pool/index.d.ts +89 -0
- package/lib/pool/index.js +527 -0
- package/lib/pool/utils.d.ts +19 -0
- package/lib/pool/utils.js +38 -0
- package/package.json +3 -3
- package/src/bpmn-elements-adapter/json2xml.ts +18 -4
- package/src/bpmn-elements-adapter/xml2json.ts +2 -8
- package/src/dynamic-group/index.ts +0 -1
- package/src/index.ts +2 -0
- package/src/pool/LaneModel.ts +226 -0
- package/src/pool/LaneView.ts +220 -0
- package/src/pool/PoolModel.ts +631 -0
- package/src/pool/PoolView.ts +75 -0
- package/src/pool/constant.ts +19 -0
- package/src/pool/index.ts +621 -0
- package/src/pool/utils.ts +46 -0
- package/stats.html +1 -1
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基于DynamicGroup重新实现的泳池节点组件
|
|
3
|
+
* 充分利用DynamicGroup的分组管理能力,实现完整的泳道功能
|
|
4
|
+
*/
|
|
5
|
+
import LogicFlow, { GraphModel, BaseEdgeModel } from '@logicflow/core'
|
|
6
|
+
import { computed } from 'mobx'
|
|
7
|
+
import { forEach, merge, cloneDeep, isEmpty } from 'lodash-es'
|
|
8
|
+
import {
|
|
9
|
+
DynamicGroupNodeModel,
|
|
10
|
+
IGroupNodeProperties,
|
|
11
|
+
} from '../dynamic-group/model'
|
|
12
|
+
import { poolConfig, laneConfig } from './constant'
|
|
13
|
+
|
|
14
|
+
// import { LaneModel } from './NewLane'
|
|
15
|
+
|
|
16
|
+
import NodeConfig = LogicFlow.NodeConfig
|
|
17
|
+
|
|
18
|
+
export class PoolModel extends DynamicGroupNodeModel {
|
|
19
|
+
// 泳池特定属性
|
|
20
|
+
// 标题区域大小:如果是垂直方向,指代的就是标题区的宽度,如果是水平方向,指代的就是标题区的高度
|
|
21
|
+
titleSize: number = poolConfig.titleSize
|
|
22
|
+
poolConfig: typeof poolConfig = poolConfig
|
|
23
|
+
readonly isPool: boolean = true
|
|
24
|
+
|
|
25
|
+
// 标记是否已创建默认泳道
|
|
26
|
+
_defaultLaneCreated: boolean = false
|
|
27
|
+
constructor(data: NodeConfig<IGroupNodeProperties>, graphModel: GraphModel) {
|
|
28
|
+
super(data, graphModel)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@computed get isHorizontal() {
|
|
32
|
+
return this.properties?.direction === 'horizontal'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
initNodeData(data: LogicFlow.NodeConfig<IGroupNodeProperties>) {
|
|
36
|
+
super.initNodeData(data)
|
|
37
|
+
if (data.properties) {
|
|
38
|
+
// 泳池基础配置
|
|
39
|
+
this.width = data.properties?.width || poolConfig.defaultWidth
|
|
40
|
+
this.height = data.properties?.height || poolConfig.defaultHeight
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 动态分组配置
|
|
44
|
+
this.autoResize = false
|
|
45
|
+
this.isRestrict = true
|
|
46
|
+
this.transformWithContainer = true
|
|
47
|
+
this.resizable = false
|
|
48
|
+
this.rotatable = false
|
|
49
|
+
this.autoToFront = false
|
|
50
|
+
|
|
51
|
+
// 允许文本编辑
|
|
52
|
+
this.text.editable = true
|
|
53
|
+
|
|
54
|
+
// 初始化文本位置
|
|
55
|
+
this.updateTextPosition()
|
|
56
|
+
this.addEventListeners()
|
|
57
|
+
this.resizePool()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 增加监听事件
|
|
61
|
+
addEventListeners() {
|
|
62
|
+
this.graphModel.eventCenter.on('node:resize', ({ data, index }) => {
|
|
63
|
+
// 如果resize的是子泳道
|
|
64
|
+
if (this.children.has(data.id)) {
|
|
65
|
+
// 检查是否为泳道节点的尺寸变化
|
|
66
|
+
const resizedNode = this.graphModel.getNodeModelById(data.id)
|
|
67
|
+
if (!resizedNode || !resizedNode.type || resizedNode.type !== 'lane') {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 获取所有子泳道
|
|
72
|
+
const lanes = this.getLanes()
|
|
73
|
+
|
|
74
|
+
// 更新泳池宽高
|
|
75
|
+
let newWidth: number
|
|
76
|
+
let newHeight: number
|
|
77
|
+
let deltaX: number = 0
|
|
78
|
+
let deltaY: number = 0
|
|
79
|
+
if (this.isHorizontal) {
|
|
80
|
+
// 横向布局:
|
|
81
|
+
// 泳池宽度 = 最大泳道宽度 + 标题区域
|
|
82
|
+
|
|
83
|
+
const totalLaneHeight = lanes.reduce(
|
|
84
|
+
(sum, lane) => sum + lane.height,
|
|
85
|
+
0,
|
|
86
|
+
)
|
|
87
|
+
newWidth = resizedNode.width + poolConfig.titleSize
|
|
88
|
+
// 泳池高度 = 所有泳道高度之和
|
|
89
|
+
newHeight = totalLaneHeight
|
|
90
|
+
} else {
|
|
91
|
+
// 竖向布局:
|
|
92
|
+
// 泳池高度 = 最大泳道高度 + 标题区域
|
|
93
|
+
const totalLaneWidth = lanes.reduce(
|
|
94
|
+
(sum, lane) => sum + lane.width,
|
|
95
|
+
0,
|
|
96
|
+
)
|
|
97
|
+
newHeight = resizedNode.height + poolConfig.titleSize
|
|
98
|
+
// 泳池宽度 = 所有泳道宽度之和
|
|
99
|
+
newWidth = totalLaneWidth
|
|
100
|
+
}
|
|
101
|
+
// 根据拖拽控制点方向计算位移方向
|
|
102
|
+
// ResizeControlIndex: 0-左上, 1-右上, 2-右下, 3-左下
|
|
103
|
+
const resizeIndex = typeof index === 'number' ? index : 2
|
|
104
|
+
const isLeft = resizeIndex === 0 || resizeIndex === 3
|
|
105
|
+
const isTop = resizeIndex === 0 || resizeIndex === 1
|
|
106
|
+
const signX = isLeft ? -1 : 1
|
|
107
|
+
const signY = isTop ? -1 : 1
|
|
108
|
+
deltaX = signX * (newWidth - this.width)
|
|
109
|
+
deltaY = signY * (newHeight - this.height)
|
|
110
|
+
this.width = newWidth
|
|
111
|
+
this.height = newHeight
|
|
112
|
+
this.move(deltaX / 2, deltaY / 2)
|
|
113
|
+
}
|
|
114
|
+
// 重新布局泳道以适应新的泳池尺寸
|
|
115
|
+
this.resizeChildren()
|
|
116
|
+
// 更新泳池文本位置
|
|
117
|
+
this.updateTextPosition()
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 获取需要移动的节点
|
|
123
|
+
* @param groupModel
|
|
124
|
+
*/
|
|
125
|
+
getNodesInGroup(groupModel: DynamicGroupNodeModel): string[] {
|
|
126
|
+
const nodeIds: string[] = []
|
|
127
|
+
if (groupModel.isGroup) {
|
|
128
|
+
forEach(Array.from(groupModel.children), (nodeId: string) => {
|
|
129
|
+
const nodeModel = this.graphModel.getNodeModelById(nodeId)
|
|
130
|
+
// 拖拽泳道时会触发泳池的getNodesInGroup,这时泳池再触发移动的子泳道里就需要剔除当前正在拖拽的泳道
|
|
131
|
+
if (nodeModel && !nodeModel.isDragging) {
|
|
132
|
+
nodeIds.push(nodeId)
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
return nodeIds
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 初始化文本位置 - 根据布局方向设置文本位置
|
|
140
|
+
*/
|
|
141
|
+
private updateTextPosition() {
|
|
142
|
+
if (this.isHorizontal) {
|
|
143
|
+
// 横向泳池:文本显示在左侧标题区域
|
|
144
|
+
this.text.x = this.x - this.width / 2 + poolConfig.titleSize / 2
|
|
145
|
+
this.text.y = this.y
|
|
146
|
+
} else {
|
|
147
|
+
// 纵向泳池:文本显示在顶部标题区域
|
|
148
|
+
this.text.x = this.x
|
|
149
|
+
this.text.y = this.y - this.height / 2 + poolConfig.titleSize / 2
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 根据子泳道自动调整泳池尺寸
|
|
155
|
+
*/
|
|
156
|
+
resizePool() {
|
|
157
|
+
const lanes = this.getLanes()
|
|
158
|
+
if (lanes.length === 0) return
|
|
159
|
+
let contentWidth = 0
|
|
160
|
+
let contentHeight = 0
|
|
161
|
+
if (this.isHorizontal) {
|
|
162
|
+
// 横向布局:计算所有泳道的边界
|
|
163
|
+
forEach(lanes, (lane) => {
|
|
164
|
+
const laneWidth = lane.width
|
|
165
|
+
const laneHeight = lane.height
|
|
166
|
+
contentWidth = Math.max(contentWidth, laneWidth)
|
|
167
|
+
contentHeight += laneHeight
|
|
168
|
+
})
|
|
169
|
+
// 计算新尺寸(横向布局:宽度包含标题区域)
|
|
170
|
+
this.width = contentWidth + poolConfig.titleSize
|
|
171
|
+
this.height = contentHeight
|
|
172
|
+
} else {
|
|
173
|
+
// 竖向布局:计算所有泳道的边界
|
|
174
|
+
forEach(lanes, (lane) => {
|
|
175
|
+
const laneWidth = lane.width
|
|
176
|
+
const laneHeight = lane.height
|
|
177
|
+
contentWidth += laneWidth
|
|
178
|
+
contentHeight = Math.max(contentHeight, laneHeight)
|
|
179
|
+
})
|
|
180
|
+
// 计算新尺寸(竖向布局:高度包含标题区域)
|
|
181
|
+
this.width = contentWidth
|
|
182
|
+
this.height = contentHeight + poolConfig.titleSize
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 更新文本位置
|
|
186
|
+
this.updateTextPosition()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 重新调整所有泳道布局
|
|
191
|
+
* @param newLanePosition 添加位置(可选):'above'|'below'|'left'|'right'
|
|
192
|
+
* @param newLaneId 新添加的泳道ID(可选)
|
|
193
|
+
*/
|
|
194
|
+
resizeChildrenWithNewLane(
|
|
195
|
+
newLanePosition?: 'above' | 'below' | 'left' | 'right',
|
|
196
|
+
newLaneId?: string,
|
|
197
|
+
) {
|
|
198
|
+
const lanes = this.getLanes()
|
|
199
|
+
const isAddingNewLane = newLanePosition && newLaneId
|
|
200
|
+
if (!isAddingNewLane || isEmpty(lanes)) return
|
|
201
|
+
let orderedLanes = [] as any[]
|
|
202
|
+
// 找到新创建的泳道
|
|
203
|
+
const newLane = lanes.find((lane) => lane.id === newLaneId)
|
|
204
|
+
// 先找到触发新增泳道的泳道的index
|
|
205
|
+
orderedLanes = lanes
|
|
206
|
+
.filter((lane) => lane.id !== newLaneId)
|
|
207
|
+
.slice()
|
|
208
|
+
.sort((a: any, b: any) => (this.isHorizontal ? a.y - b.y : a.x - b.x))
|
|
209
|
+
if (newLane) {
|
|
210
|
+
const refId = (newLane as any).properties?.referenceLaneId
|
|
211
|
+
const refIndex = refId
|
|
212
|
+
? orderedLanes.findIndex((l: any) => l.id === refId)
|
|
213
|
+
: 0
|
|
214
|
+
const insertIndex = ['above', 'left'].includes(newLanePosition)
|
|
215
|
+
? Math.max(refIndex, 0)
|
|
216
|
+
: Math.min(refIndex + 1, orderedLanes.length)
|
|
217
|
+
// 按顺序插入新泳道
|
|
218
|
+
orderedLanes.splice(insertIndex, 0, newLane)
|
|
219
|
+
}
|
|
220
|
+
if (this.isHorizontal) {
|
|
221
|
+
// 统一泳道宽度
|
|
222
|
+
const laneWidth = this.width - poolConfig.titleSize
|
|
223
|
+
// 计算泳道在内容区域内的分布
|
|
224
|
+
const newHeight = orderedLanes.reduce((sum: number, lane: any) => {
|
|
225
|
+
return sum + lane.height
|
|
226
|
+
}, 0)
|
|
227
|
+
let laneTopDistance: number = this.y - newHeight / 2
|
|
228
|
+
orderedLanes.forEach((lane: any, index: number) => {
|
|
229
|
+
const newLaneY = laneTopDistance + lane.height / 2
|
|
230
|
+
// 统一泳道文本位置
|
|
231
|
+
lane.text = {
|
|
232
|
+
...lane.text,
|
|
233
|
+
x: lane.x - laneWidth / 2 + laneConfig.titleSize / 2,
|
|
234
|
+
y: lane.y,
|
|
235
|
+
}
|
|
236
|
+
this.moveLane(lane, lane.x, newLaneY)
|
|
237
|
+
// 为下一个泳道计算Y坐标
|
|
238
|
+
if (index < orderedLanes.length - 1) {
|
|
239
|
+
laneTopDistance += lane.height
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
this.height = newHeight
|
|
243
|
+
} else {
|
|
244
|
+
// 统一泳道高度
|
|
245
|
+
const laneHeight = this.height - poolConfig.titleSize
|
|
246
|
+
const newWidth = orderedLanes.reduce((sum: number, lane: any) => {
|
|
247
|
+
return sum + lane.width
|
|
248
|
+
}, 0)
|
|
249
|
+
let laneLeftDistance: number = this.x - newWidth / 2
|
|
250
|
+
// 遍历所有泳道,设置它们的位置
|
|
251
|
+
orderedLanes.forEach((lane: any, index: number) => {
|
|
252
|
+
const newLaneX = laneLeftDistance + lane.width / 2
|
|
253
|
+
// 统一泳道文本位置
|
|
254
|
+
lane.text = {
|
|
255
|
+
...lane.text,
|
|
256
|
+
x: lane.x,
|
|
257
|
+
y: lane.y - laneHeight / 2 + laneConfig.titleSize / 2,
|
|
258
|
+
}
|
|
259
|
+
this.moveLane(lane, newLaneX, lane.y)
|
|
260
|
+
// 为下一个泳道计算X坐标
|
|
261
|
+
if (index < orderedLanes.length - 1) {
|
|
262
|
+
laneLeftDistance += lane.width
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
this.width = newWidth
|
|
266
|
+
}
|
|
267
|
+
// 更新文本位置
|
|
268
|
+
this.updateTextPosition()
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
moveLane(lane: any, newX, newY) {
|
|
272
|
+
// 更新泳子节点位置
|
|
273
|
+
const childrenRelPos: { id: string; dx: number; dy: number }[] = []
|
|
274
|
+
if (lane.children && lane.children.size > 0) {
|
|
275
|
+
lane.children.forEach((childId: string) => {
|
|
276
|
+
const childNode = this.graphModel.getNodeModelById(childId)
|
|
277
|
+
// 过滤掉拖拽中的节点和 Lane 类型(避免递归)
|
|
278
|
+
if (
|
|
279
|
+
childNode &&
|
|
280
|
+
!childNode.isDragging &&
|
|
281
|
+
String(childNode.type) !== 'lane'
|
|
282
|
+
) {
|
|
283
|
+
childrenRelPos.push({
|
|
284
|
+
id: childId,
|
|
285
|
+
dx: childNode.x - lane.x,
|
|
286
|
+
dy: childNode.y - lane.y,
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
// 设置泳道位置和尺寸
|
|
292
|
+
lane.moveTo(newX, newY, true)
|
|
293
|
+
childrenRelPos.forEach(({ id, dx, dy }) => {
|
|
294
|
+
const childNode = this.graphModel.getNodeModelById(id)
|
|
295
|
+
if (childNode) {
|
|
296
|
+
const { x, y } = childNode
|
|
297
|
+
const newChildX = lane.x + dx
|
|
298
|
+
const newChildY = lane.y + dy
|
|
299
|
+
childNode.moveTo(newChildX, newChildY)
|
|
300
|
+
const { edges: incomingEdges } = childNode.incoming
|
|
301
|
+
const { edges: outgoingEdges } = childNode.outgoing
|
|
302
|
+
incomingEdges.forEach((edge: BaseEdgeModel) => {
|
|
303
|
+
edge.moveEndPoint(newChildX - x, newChildY - y)
|
|
304
|
+
})
|
|
305
|
+
outgoingEdges.forEach((edge: BaseEdgeModel) => {
|
|
306
|
+
edge.moveStartPoint(newChildX - x, newChildY - y)
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 重新调整所有泳道布局
|
|
314
|
+
* @param newLanePosition 添加位置(可选):'above'|'below'|'left'|'right'
|
|
315
|
+
* @param newLaneId 新添加的泳道ID(可选)
|
|
316
|
+
*/
|
|
317
|
+
resizeChildren() {
|
|
318
|
+
// 遍历所有泳道,horizontal泳道按Y轴排序,vertical泳道按X轴排序并调整位置
|
|
319
|
+
const lanes = this.getLanes()
|
|
320
|
+
if (lanes.length === 0) return
|
|
321
|
+
|
|
322
|
+
if (this.isHorizontal) {
|
|
323
|
+
this.height = lanes.reduce((sum: number, lane: any) => {
|
|
324
|
+
return sum + lane.height
|
|
325
|
+
}, 0)
|
|
326
|
+
// 遍历所有泳道,产出它们在y轴从上到下的顺序
|
|
327
|
+
const orderedLanes = lanes.slice().sort((a: any, b: any) => a.y - b.y)
|
|
328
|
+
lanes.forEach((lane: any) => {
|
|
329
|
+
lane.width = this.width - poolConfig.titleSize
|
|
330
|
+
const laneIndex = orderedLanes.findIndex(
|
|
331
|
+
(orderedLane: any) => orderedLane.id === lane.id,
|
|
332
|
+
)
|
|
333
|
+
// 遍历orderedLanes,计算出lane相比泳池顶部的距离
|
|
334
|
+
const laneTopDistance = orderedLanes.reduce(
|
|
335
|
+
(sum: number, orderedLane: any, index: number) => {
|
|
336
|
+
if (index < laneIndex && orderedLane.id !== lane.id) {
|
|
337
|
+
return sum + orderedLane.height
|
|
338
|
+
}
|
|
339
|
+
return sum
|
|
340
|
+
},
|
|
341
|
+
this.y - this.height / 2,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
lane.moveTo(
|
|
345
|
+
this.x - this.width / 2 + poolConfig.titleSize + lane.width / 2,
|
|
346
|
+
laneTopDistance + lane.height / 2,
|
|
347
|
+
true,
|
|
348
|
+
)
|
|
349
|
+
})
|
|
350
|
+
} else {
|
|
351
|
+
this.width = lanes.reduce((sum: number, lane: any) => {
|
|
352
|
+
return sum + lane.width
|
|
353
|
+
}, 0)
|
|
354
|
+
// 垂直泳道按X轴排序
|
|
355
|
+
const orderedLanes = cloneDeep(lanes).sort((a: any, b: any) => a.x - b.x)
|
|
356
|
+
lanes.forEach((lane: any) => {
|
|
357
|
+
lane.height = this.height - poolConfig.titleSize
|
|
358
|
+
const laneIndex = orderedLanes.findIndex(
|
|
359
|
+
(orderedLane: any) => orderedLane.id === lane.id,
|
|
360
|
+
)
|
|
361
|
+
// 遍历orderedLanes,计算出lane相比泳池顶部的距离
|
|
362
|
+
const laneLeftDistance = orderedLanes.reduce(
|
|
363
|
+
(sum: number, orderedLane: any, index: number) => {
|
|
364
|
+
if (index < laneIndex && orderedLane.id !== lane.id) {
|
|
365
|
+
return sum + orderedLane.width
|
|
366
|
+
}
|
|
367
|
+
return sum
|
|
368
|
+
},
|
|
369
|
+
this.x - this.width / 2,
|
|
370
|
+
)
|
|
371
|
+
lane.moveTo(
|
|
372
|
+
laneLeftDistance + lane.width / 2,
|
|
373
|
+
this.y - this.height / 2 + poolConfig.titleSize + lane.height / 2,
|
|
374
|
+
true,
|
|
375
|
+
)
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* 获取子泳道
|
|
382
|
+
*/
|
|
383
|
+
getLanes() {
|
|
384
|
+
const children: any[] = []
|
|
385
|
+
Array.from(this.children).forEach((childId) => {
|
|
386
|
+
const childModel = this.graphModel.getNodeModelById(childId)
|
|
387
|
+
if (childModel && String(childModel.type) === 'lane') {
|
|
388
|
+
children.push(childModel)
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
return children
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 添加泳道的公共方法
|
|
396
|
+
* @param position 添加位置:'above'|'below'|'left'|'right'
|
|
397
|
+
* @param laneData 泳道数据
|
|
398
|
+
*/
|
|
399
|
+
addLane(position: 'above' | 'below' | 'left' | 'right', laneData?: any) {
|
|
400
|
+
const lanes = this.getLanes()
|
|
401
|
+
if (lanes.length === 0) {
|
|
402
|
+
return this.createDefaultLane(laneData)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 计算初始位置
|
|
406
|
+
let initialX = this.x
|
|
407
|
+
let initialY = this.y
|
|
408
|
+
// 参考泳道(用于定位)
|
|
409
|
+
const referenceLane = lanes.find((lane) => lane.id === laneData?.id)
|
|
410
|
+
// 用于确定新泳道尺寸的参考泳道,优先使用referenceLane,其次使用现有第一个泳道,最后回退到泳池尺寸
|
|
411
|
+
const sizeLane = referenceLane || lanes[0]
|
|
412
|
+
const laneWidth = sizeLane?.width ?? this.width
|
|
413
|
+
const laneHeight = sizeLane?.height ?? this.height
|
|
414
|
+
|
|
415
|
+
if (this.isHorizontal && ['above', 'below'].includes(position)) {
|
|
416
|
+
if (referenceLane) {
|
|
417
|
+
initialY =
|
|
418
|
+
position === 'above'
|
|
419
|
+
? referenceLane.y - referenceLane.height / 2 - laneHeight / 2
|
|
420
|
+
: referenceLane.y + referenceLane.height / 2 + laneHeight / 2
|
|
421
|
+
initialX = referenceLane.x
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (!this.isHorizontal && ['left', 'right'].includes(position)) {
|
|
425
|
+
if (referenceLane) {
|
|
426
|
+
initialX =
|
|
427
|
+
position === 'left'
|
|
428
|
+
? referenceLane.x - referenceLane.width / 2 - laneWidth / 2
|
|
429
|
+
: referenceLane.x + referenceLane.width / 2 + laneWidth / 2
|
|
430
|
+
initialY = referenceLane.y
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// 确保不将referenceLaneId作为parent或者其他可能引起递归引用的属性传入
|
|
435
|
+
// laneData可能包含一些运行时属性,需要清理
|
|
436
|
+
const cleanLaneData = cloneDeep(laneData)
|
|
437
|
+
if (cleanLaneData) {
|
|
438
|
+
delete cleanLaneData.id
|
|
439
|
+
delete cleanLaneData.children
|
|
440
|
+
delete cleanLaneData.properties?.parent
|
|
441
|
+
delete cleanLaneData.properties?.children
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const nodeConfig = merge(
|
|
445
|
+
cleanLaneData,
|
|
446
|
+
{
|
|
447
|
+
type: 'lane',
|
|
448
|
+
x: initialX,
|
|
449
|
+
y: initialY,
|
|
450
|
+
width: laneWidth,
|
|
451
|
+
height: laneHeight,
|
|
452
|
+
text: '新泳道',
|
|
453
|
+
properties: {
|
|
454
|
+
parent: this.id, // 确保父节点始终指向泳池
|
|
455
|
+
position: position, // 记录添加位置,供resizeChildren使用
|
|
456
|
+
referenceLaneId: referenceLane?.id, // 记录参考泳道ID
|
|
457
|
+
},
|
|
458
|
+
zIndex: this.zIndex,
|
|
459
|
+
},
|
|
460
|
+
this.properties.laneConfig,
|
|
461
|
+
)
|
|
462
|
+
const newLane = this.graphModel.addNode(nodeConfig)
|
|
463
|
+
this.setZIndex(this.zIndex - 1)
|
|
464
|
+
this.addChild(newLane.id)
|
|
465
|
+
|
|
466
|
+
// 调用优化后的resizeChildren,它会处理所有位置计算和泳池尺寸调整
|
|
467
|
+
this.resizeChildrenWithNewLane(position, newLane.id)
|
|
468
|
+
return newLane
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
setZIndex(zIndex: number) {
|
|
472
|
+
// this.zIndex = zIndex
|
|
473
|
+
this.zIndex = Math.min(zIndex, -100)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* 在上方添加泳道
|
|
478
|
+
*/
|
|
479
|
+
addChildAbove(laneData?: any) {
|
|
480
|
+
return this.addLane('above', laneData)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* 在下方添加泳道
|
|
485
|
+
*/
|
|
486
|
+
addChildBelow(laneData?: any) {
|
|
487
|
+
return this.addLane('below', laneData)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* 在左侧添加泳道
|
|
492
|
+
*/
|
|
493
|
+
addChildLeft(laneData?: any) {
|
|
494
|
+
return this.addLane('left', laneData)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* 在右侧添加泳道(纵向布局专用)
|
|
499
|
+
*/
|
|
500
|
+
addChildRight(laneData?: any) {
|
|
501
|
+
return this.addLane('right', laneData)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* 创建默认泳道
|
|
506
|
+
*/
|
|
507
|
+
createDefaultLane(laneConfig?: any) {
|
|
508
|
+
let newLane: any = null
|
|
509
|
+
// 只在没有子节点时创建默认泳道
|
|
510
|
+
if (this.isHorizontal) {
|
|
511
|
+
// 横向泳池:泳道垂直排列
|
|
512
|
+
const laneWidth = this.width - poolConfig.titleSize
|
|
513
|
+
const laneHeight = this.height
|
|
514
|
+
newLane = this.graphModel.addNode(
|
|
515
|
+
merge(
|
|
516
|
+
{
|
|
517
|
+
type: 'lane',
|
|
518
|
+
x: this.x - this.width / 2 + poolConfig.titleSize + laneWidth / 2,
|
|
519
|
+
y: this.y,
|
|
520
|
+
width: laneWidth,
|
|
521
|
+
height: laneHeight,
|
|
522
|
+
text: {
|
|
523
|
+
x: this.x - this.width / 2 + poolConfig.titleSize / 2,
|
|
524
|
+
y: this.y,
|
|
525
|
+
value: '泳道1',
|
|
526
|
+
},
|
|
527
|
+
properties: {
|
|
528
|
+
parent: this.id,
|
|
529
|
+
isHorizontal: this.isHorizontal,
|
|
530
|
+
},
|
|
531
|
+
zIndex: this.zIndex,
|
|
532
|
+
},
|
|
533
|
+
laneConfig,
|
|
534
|
+
),
|
|
535
|
+
)
|
|
536
|
+
} else {
|
|
537
|
+
// 纵向泳池:泳道水平排列
|
|
538
|
+
// 修复:初始泳道在泳池中心位置,与resizeChildren逻辑保持一致
|
|
539
|
+
const laneWidth = this.width
|
|
540
|
+
const laneHeight = this.height - poolConfig.titleSize
|
|
541
|
+
newLane = this.graphModel.addNode(
|
|
542
|
+
merge(
|
|
543
|
+
{
|
|
544
|
+
type: 'lane',
|
|
545
|
+
x: this.x,
|
|
546
|
+
y:
|
|
547
|
+
this.y -
|
|
548
|
+
this.height / 2 +
|
|
549
|
+
poolConfig.titleSize +
|
|
550
|
+
(this.height - poolConfig.titleSize) / 2,
|
|
551
|
+
width: laneWidth,
|
|
552
|
+
height: laneHeight,
|
|
553
|
+
text: {
|
|
554
|
+
x: this.x,
|
|
555
|
+
y: this.y - this.height / 2 + poolConfig.titleSize / 2,
|
|
556
|
+
value: '泳道1',
|
|
557
|
+
},
|
|
558
|
+
properties: {
|
|
559
|
+
parent: this.id,
|
|
560
|
+
},
|
|
561
|
+
zIndex: this.zIndex,
|
|
562
|
+
},
|
|
563
|
+
laneConfig,
|
|
564
|
+
),
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
this.setZIndex(this.zIndex - 1)
|
|
568
|
+
this.addChild(newLane.id)
|
|
569
|
+
this.resizeChildren()
|
|
570
|
+
this.updateTextPosition()
|
|
571
|
+
return newLane
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* 删除泳道
|
|
576
|
+
*/
|
|
577
|
+
deleteChild(childId: string) {
|
|
578
|
+
const lanes = this.getLanes()
|
|
579
|
+
if (lanes.length <= 1) return
|
|
580
|
+
|
|
581
|
+
const laneToDelete = lanes.find((lane) => lane.id === childId)
|
|
582
|
+
if (!laneToDelete) return
|
|
583
|
+
|
|
584
|
+
// 移除子节点
|
|
585
|
+
this.removeChild(childId)
|
|
586
|
+
this.graphModel.deleteNode(childId)
|
|
587
|
+
|
|
588
|
+
// 重新调整泳池
|
|
589
|
+
this.resizePool()
|
|
590
|
+
this.resizeChildren()
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
getNodeStyle() {
|
|
594
|
+
const style = super.getNodeStyle()
|
|
595
|
+
style.strokeWidth = 2
|
|
596
|
+
return style
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* 获取文本样式
|
|
601
|
+
*/
|
|
602
|
+
getTextStyle() {
|
|
603
|
+
const style = super.getTextStyle()
|
|
604
|
+
style.overflowMode = 'ellipsis'
|
|
605
|
+
style.strokeWidth = 2
|
|
606
|
+
style.textWidth = this.isHorizontal ? this.height : this.width
|
|
607
|
+
style.textHeight = this.isHorizontal ? this.width : this.height
|
|
608
|
+
if (this.isHorizontal) {
|
|
609
|
+
style.transform = 'rotate(-90deg)'
|
|
610
|
+
style.textAlign = 'center'
|
|
611
|
+
}
|
|
612
|
+
return style
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
getData(): LogicFlow.NodeData {
|
|
616
|
+
const data = super.getData()
|
|
617
|
+
// const poolModel = this.getPoolModel()
|
|
618
|
+
return {
|
|
619
|
+
...data,
|
|
620
|
+
properties: {
|
|
621
|
+
...data.properties,
|
|
622
|
+
width: this.width,
|
|
623
|
+
height: this.height,
|
|
624
|
+
},
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export default {
|
|
630
|
+
PoolModel,
|
|
631
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { h } from '@logicflow/core'
|
|
2
|
+
import { DynamicGroupNode } from '../dynamic-group/node'
|
|
3
|
+
import { poolConfig } from './constant'
|
|
4
|
+
|
|
5
|
+
export class PoolView extends DynamicGroupNode {
|
|
6
|
+
componentDidMount(): void {
|
|
7
|
+
const { graphModel, model } = this.props
|
|
8
|
+
const index = graphModel.nodes.findIndex((node) => node.id === model.id)
|
|
9
|
+
const poolCount = graphModel.nodes.filter(
|
|
10
|
+
(node) => String(node.type) === 'pool',
|
|
11
|
+
).length
|
|
12
|
+
// 设置一个足够低的z-index,确保泳池在所有节点的最底层
|
|
13
|
+
model.setZIndex(-((poolCount - index) * 100))
|
|
14
|
+
if (
|
|
15
|
+
!model.properties?.children?.length &&
|
|
16
|
+
!model._defaultLaneCreated &&
|
|
17
|
+
!model.virtual
|
|
18
|
+
) {
|
|
19
|
+
model.createDefaultLane(model.properties?.laneConfig)
|
|
20
|
+
model._defaultLaneCreated = true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 渲染泳池形状 - 根据布局方向分为标题区域和内容区域
|
|
26
|
+
*/
|
|
27
|
+
getShape() {
|
|
28
|
+
const { model } = this.props
|
|
29
|
+
const {
|
|
30
|
+
x,
|
|
31
|
+
y,
|
|
32
|
+
width,
|
|
33
|
+
height,
|
|
34
|
+
properties: { textStyle: customTextStyle = {}, style: customStyle = {} },
|
|
35
|
+
isHorizontal,
|
|
36
|
+
} = model
|
|
37
|
+
const style = model.getNodeStyle()
|
|
38
|
+
const base = { fill: '#ffffff', stroke: '#000000', strokeWidth: 1 }
|
|
39
|
+
const left = x - width / 2
|
|
40
|
+
const top = y - height / 2
|
|
41
|
+
|
|
42
|
+
const titleRect = {
|
|
43
|
+
...base,
|
|
44
|
+
...style,
|
|
45
|
+
x: left,
|
|
46
|
+
y: top,
|
|
47
|
+
width: isHorizontal ? poolConfig.titleSize : width,
|
|
48
|
+
height: isHorizontal ? height : poolConfig.titleSize,
|
|
49
|
+
...(isHorizontal && customTextStyle),
|
|
50
|
+
}
|
|
51
|
+
const contentRect = {
|
|
52
|
+
...base,
|
|
53
|
+
...style,
|
|
54
|
+
x: isHorizontal ? left + poolConfig.titleSize : left,
|
|
55
|
+
y: isHorizontal ? top : top + poolConfig.titleSize,
|
|
56
|
+
width: isHorizontal ? width - poolConfig.titleSize : width,
|
|
57
|
+
height: isHorizontal ? height : height - poolConfig.titleSize,
|
|
58
|
+
...(isHorizontal && customStyle),
|
|
59
|
+
}
|
|
60
|
+
return h('g', {}, [h('rect', titleRect), h('rect', contentRect)])
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 获取调整控制点 - 只在展开状态下显示
|
|
65
|
+
*/
|
|
66
|
+
getResizeControl() {
|
|
67
|
+
const { resizable, isCollapsed } = this.props.model
|
|
68
|
+
const showResizeControl = resizable && !isCollapsed
|
|
69
|
+
return showResizeControl ? super.getResizeControl() : null
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default {
|
|
74
|
+
PoolView,
|
|
75
|
+
}
|