-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Closed
Labels
bugSomething isn't workingSomething isn't working
Description
发生了什么?
自定义了两个node,泳池和泳道。
泳池是groupNode;
泳道node在泳池中;
在画布上拖拽泳道,泳池在画布上会跟随泳道同步缩放
问题是:
拖拽泳道后,小地图中没有同步更新泳池的最新数据,保存数据后发现,数据跟小地图一样没有同步更新。
排查思路:
在画布上拖拽泳道,虽然画布中的泳池变化了,但是泳池的数据没有同步更新,所以对泳池进行changeAttribute,或setAttribute,或setProperties都没有效果
核心代码如下:
// index.vue
lf.on('node:resize', ({preData: oldNodeSize, data: newNodeSize}) => {
// 省略其他代码
lf.getNodeModelById(id).resizeChildren({resizeDir, deltaWidth})
})
// 这里是泳池的文件 Pool.js
/**
* 泳道节点
*/
import { h } from '@logicflow/core';
import { GroupNode, GroupNodeModel } from '@logicflow/extension';
import nodeConfig from './nodeConfig';
const laneMinSize = {
width: 72,
height: 400
// width: 312,
// height: 72
}
const addChildAboveNum = 200
class HorizontalLaneModel extends GroupNodeModel {
initNodeData(data) {
super.initNodeData(data);
this.height = 300
this.width = addChildAboveNum
// this.foldable = true
this.foldedWidth = 160
// this.height= 260
// // this.foldable = true
// this.foldedWidth = 42
this.resizable = false
this.zIndex = 1
this.text.editable = true
}
setAttributes() {
this.text = {
...this.text,
value: this.text.value || 'XXX流程图',
x: this.x,
y: this.y - this.height/2 - 15
}
}
getTextStyle() {
const style = super.getTextStyle()
style.textWidth = 16
return style
}
foldGroup(isFolded) {
this.setProperty('isFolded', isFolded);
this.isFolded = isFolded;
// step 1
if (isFolded) {
this.x = this.x - this.width / 2 + this.foldedWidth / 2;
this.unfoldedWidth = this.width;
this.unfoldedHight = this.height;
this.width = this.foldedWidth;
} else {
this.width = this.unfoldedWidth;
this.x = this.x + this.width / 2 - this.foldedWidth / 2;
}
// step 2
let allEdges = this.incoming.edges.concat(this.outgoing.edges);
this.children.forEach((elementId) => {
const nodeModel = this.graphModel.getElement(elementId);
nodeModel.visible = !isFolded;
allEdges = allEdges.concat(nodeModel.incoming.edges.concat(nodeModel.outgoing.edges));
});
// step 3
this.foldEdge(isFolded, allEdges);
}
// 感应泳道变化,调整宽高
resize(resizeId, newNodeSize) {
if (!this.children.size) {
return
}
let minX = null
let maxX = null
let minY = null
let maxY = null
let hasMaxX = false
let hasMaxY = false
// 找到边界
this.children.forEach((elementId) => {
const nodeModel = this.graphModel.getElement(elementId);
const {x,y, width, height, type, id} = nodeModel
if (type !== 'lane') {
return
}
// if (id === resizeId) {
// minX = newNodeSize.x - newNodeSize.width/2
// maxX = newNodeSize.x + newNodeSize.width/2
// hasMaxX = true
// }
// if (!hasMaxX && (!minX || (x - width/2 < minX))) {
// minX = x - width/2
// }
// if (!hasMaxX && (!maxX || (x + width/2 > maxX))) {
// maxX = x + width/2
// }
// if (!minY || (y - height/2 < minY)) {
// minY = y - height/2
// }
// if (!maxY || (y + height/2 > maxY)) {
// maxY = y + height/2
// }
if (id === resizeId) {
minY = newNodeSize.y - newNodeSize.height/2
maxY = newNodeSize.y + newNodeSize.height/2
hasMaxY = true
}
if (!hasMaxY && (!minY || (y - height/2 < minY))) {
minY = y - height/2
}
if (!hasMaxY && (!maxY || (y + height/2 > maxY))) {
maxY = y + height/2
}
if (!minX || (x - width/2 < minX)) {
minX = x - width/2
}
if (!maxX || (x + width/2 > maxX)) {
maxX = x + width/2
}
})
if (minX && maxX && minY && maxY) {
this.width = maxX - minX
this.height = maxY - minY
this.x = minX + (maxX - minX) /2
this.y = minY + (maxY - minY) /2
this.resizeChildren({})
this.setAttributes()
console.log('m--=')
}
}
resizeChildren({resizeDir='', deltaHeight=0, deltaWidth=0}) {
const {x,y,width, height} = this
const laneChildren = []
this.children.forEach(elementId => {
const nodeModel = this.graphModel.getElement(elementId)
const {type} = nodeModel
if (type === 'lane') {
laneChildren.push(nodeModel)
}
})
// 按照位置排序
laneChildren.sort((a,b) => {
// if (a.y < b.y) {
if (a.x < b.x) {
return -1
} else {
return 1
}
})
// 把泳池resize的高度加进来
/**
* 泳道节点
*/
import { h } from '@logicflow/core';
import { GroupNode, GroupNodeModel } from '@logicflow/extension';
import nodeConfig from './nodeConfig';
const laneMinSize = {
width: 72,
height: 400
// width: 312,
// height: 72
}
const addChildAboveNum = 200
class HorizontalLaneModel extends GroupNodeModel {
initNodeData(data) {
super.initNodeData(data);
this.height = 300
this.width = addChildAboveNum
// this.foldable = true
this.foldedWidth = 160
// this.height= 260
// // this.foldable = true
// this.foldedWidth = 42
this.resizable = false
this.zIndex = 1
this.text.editable = true
}
setAttributes() {
this.text = {
...this.text,
value: this.text.value || 'XXX流程图',
x: this.x,
y: this.y - this.height/2 - 15
}
}
getTextStyle() {
const style = super.getTextStyle()
style.textWidth = 16
return style
}
foldGroup(isFolded) {
this.setProperty('isFolded', isFolded);
this.isFolded = isFolded;
// step 1
if (isFolded) {
this.x = this.x - this.width / 2 + this.foldedWidth / 2;
this.unfoldedWidth = this.width;
this.unfoldedHight = this.height;
this.width = this.foldedWidth;
} else {
this.width = this.unfoldedWidth;
this.x = this.x + this.width / 2 - this.foldedWidth / 2;
}
// step 2
let allEdges = this.incoming.edges.concat(this.outgoing.edges);
this.children.forEach((elementId) => {
const nodeModel = this.graphModel.getElement(elementId);
nodeModel.visible = !isFolded;
allEdges = allEdges.concat(nodeModel.incoming.edges.concat(nodeModel.outgoing.edges));
});
// step 3
this.foldEdge(isFolded, allEdges);
}
// 感应泳道变化,调整宽高
resize(resizeId, newNodeSize) {
if (!this.children.size) {
return
}
let minX = null
let maxX = null
let minY = null
let maxY = null
let hasMaxX = false
let hasMaxY = false
// 找到边界
this.children.forEach((elementId) => {
const nodeModel = this.graphModel.getElement(elementId);
const {x,y, width, height, type, id} = nodeModel
if (type !== 'lane') {
return
}
// if (id === resizeId) {
// minX = newNodeSize.x - newNodeSize.width/2
// maxX = newNodeSize.x + newNodeSize.width/2
// hasMaxX = true
// }
// if (!hasMaxX && (!minX || (x - width/2 < minX))) {
// minX = x - width/2
// }
// if (!hasMaxX && (!maxX || (x + width/2 > maxX))) {
// maxX = x + width/2
// }
// if (!minY || (y - height/2 < minY)) {
// minY = y - height/2
// }
// if (!maxY || (y + height/2 > maxY)) {
// maxY = y + height/2
// }
if (id === resizeId) {
minY = newNodeSize.y - newNodeSize.height/2
maxY = newNodeSize.y + newNodeSize.height/2
hasMaxY = true
}
if (!hasMaxY && (!minY || (y - height/2 < minY))) {
minY = y - height/2
}
if (!hasMaxY && (!maxY || (y + height/2 > maxY))) {
maxY = y + height/2
}
if (!minX || (x - width/2 < minX)) {
minX = x - width/2
}
if (!maxX || (x + width/2 > maxX)) {
maxX = x + width/2
}
})
if (minX && maxX && minY && maxY) {
this.width = maxX - minX
this.height = maxY - minY
this.x = minX + (maxX - minX) /2
this.y = minY + (maxY - minY) /2
this.resizeChildren({})
this.setAttributes()
console.log('m--=')
}
}
resizeChildren({resizeDir='', deltaHeight=0, deltaWidth=0}) {
const {x,y,width, height} = this
const laneChildren = []
this.children.forEach(elementId => {
const nodeModel = this.graphModel.getElement(elementId)
const {type} = nodeModel
if (type === 'lane') {
laneChildren.push(nodeModel)
}
})
// 按照位置排序
laneChildren.sort((a,b) => {
// if (a.y < b.y) {
if (a.x < b.x) {
return -1
} else {
return 1
}
})
// 把泳池resize的高度加进来
switch(resizeDir) {
case 'below':
// 高度加在最下面的泳道上
const lastLane = laneChildren[laneChildren.length - 1]
console.log('lastLane-=-=', lastLane)
// lastLane.height = lastLane.height + deltaHeight < laneMinSize.height ? laneMinSize.height: lastLane.height + deltaHeight
lastLane.width = lastLane.width + deltaWidth < laneMinSize.width ? laneMinSize.width: lastLane.width + deltaWidth
laneChildren[laneChildren.length - 1] = lastLane
break;
case 'above':
// 高度加在最上面的泳道上
const firstLane = laneChildren[0]
console.log('firstLane-=-=', firstLane)
// firstLane.height = firstLane.height + deltaHeight < laneMinSize.height ? laneMinSize.height: firstLane.height + deltaHeight
firstLane.width = firstLane.width + deltaWidth < laneMinSize.width ? laneMinSize.width: firstLane.width + deltaWidth
laneChildren[0] = firstLane
break;
default: break;
}
// const poolHeight = laneChildren.reduce((a,b) => {
// return a + b.height
// },0)
const poolWidth = laneChildren.reduce((a,b) => {
return a + b.width
},0)
// let aboveNodeHeights = 0
let aboveNodeWidths = 0
laneChildren.forEach((nodeModel, index) => {
const {width} = nodeModel
console.log('width, height-=-=', width, height)
nodeModel.changeAttribute({
width,
height,
x: x - poolWidth/2 + aboveNodeWidths + width/2,
y: y
// width: width - 30,
// height,
// x: x + 15,
// y: y - poolHeight/2 + aboveNodeHeights + height / 2
})
// aboveNodeHeights += height
aboveNodeWidths += width
nodeModel.setAttributes()
})
this.height = height
this.width = poolWidth
this.y = y
}
addChild (childId) {
super.addChild(childId)
this.graphModel.group.nodeGroupMap?.set(childId, this.id);
this.setAttributes()
}
addChildAbove({ x, y, width, height}) {
this.children.forEach(elementId => {
const nodeModel = this.graphModel.getElement(elementId)
const {type, y: childY, x: childX} = nodeModel
if (type !== 'lane') {
return
}
// 在被操作的泳道之上
// if (childY < y) {
// nodeModel.changeAttribute({y: childY-addChildAboveNum})
// }
if (childX < x) {
nodeModel.changeAttribute({x: childX - addChildAboveNum})
}
})
const {id:laneId} = this.graphModel.addNode({
type: 'lane',
properties: {
nodeSize: {
width: addChildAboveNum,
height,
// width: width,
// height: addChildAboveNum
}
},
x: x -width/2 - addChildAboveNum / 2,
y,
// x,
// y: y -height/2 - 60,
})
this.width = this.width + addChildAboveNum
this.x = this.x - addChildAboveNum / 2
// this.height = this.height + addChildAboveNum
// this.y = this.y - addChildAboveNum / 2
this.addChild(laneId)
this.children.forEach(elementId => {
const nodeModel = this.graphModel.getElement(elementId)
const {type, y: childY, x: childX} = nodeModel
if (type !== 'lane') {
return
}
nodeModel.setAttributes()
})
}
addChildBelow({ x, y, width, height}) {
this.children.forEach(elementId => {
const nodeModel = this.graphModel.getElement(elementId)
const {type, y:childY, x: childX} = nodeModel
if (type !== 'lane') {
return
}
// 在被操作的泳道之下
// if (childY > y) {
// nodeModel.changeAttribute({y: childY+addChildAboveNum})
// }
if (childX > x) {
nodeModel.changeAttribute({x: childX+addChildAboveNum})
}
})
const {id:laneId} = this.graphModel.addNode({
type: 'lane',
properties: {
nodeSize: {
// width: width,
// height: addChildAboveNum
width: addChildAboveNum,
height
}
},
x: x + width/2 + addChildAboveNum / 2,
y
// x,
// y: y + height/2 + addChildAboveNum / 2,
})
this.width = this.width + addChildAboveNum
this.x = this.x + addChildAboveNum / 2
// this.height = this.height + addChildAboveNum
// this.y = this.y + addChildAboveNum / 2
this.addChild(laneId)
this.children.forEach(elementId => {
const nodeModel = this.graphModel.getElement(elementId)
const {type, y: childY, x: childX} = nodeModel
if (type !== 'lane') {
return
}
nodeModel.setAttributes()
})
}
deleteChild(childId, pId) {
const laneChildren = []
this.children.forEach(elementId => {
const nodeModel = this.graphModel.getElement(elementId)
const {type} = nodeModel
if (type === 'lane') {
laneChildren.push(nodeModel)
}
})
this.removeChild(childId)
this.graphModel.deleteNode(childId)
if (laneChildren.length <= 1) {
this.removeChild(pId)
this.graphModel.deleteNode(pId)
// return
}
this.resize()
}
getNodeStyle() {
const style = super.getNodeStyle();
style.stroke = nodeConfig.stroke;
style.strokeWidth = 1;
return style;
}
getDefaultAnchor() {
return [];
}
}
class HorizontalLaneView extends GroupNode {
getResizeShape() {
const {model} = this.props
const {x, y, width, height} =model
const style = model.getNodeStyle()
// 标题区域
const foldRectAttrs = {
...style,
x: x-width/2,
y: y-height/2 - 30,
width: width,
height: 30,
}
// 泳道区域
const transRectAttrs = {
...style,
x: x - width/2,
y: y - height/2,
width: width,
height,
fill: 'transparent',
}
return h('g', {}, [
// this.getAddAbleShape(),
// h('rect', {...foldRectAttrs}),
h('rect', {...transRectAttrs}),
this.getFoldIcon()
])
}
}
const PoolNode = {
type: 'pool',
view: HorizontalLaneView,
model: HorizontalLaneModel,
}
export default PoolNode
// 这里是lane.js
mport { h } from '@logicflow/core'
import { GroupNode, GroupNodeModel } from '@logicflow/extension';
import nodeConfig from './nodeConfig';
// 泳道
class LaneModel extends GroupNodeModel {
initNodeData(data) {
super.initNodeData(data)
if (data.width) {
this.width = data.width
}
if (data.height) {
this.height = data.height
}
this.draggable = false
this.text.editable = true
this.resizable = true
this.zIndex = 1
this.setAttributes()
}
changeAttribute({width, height, x, y}) {
if(width) this.width = width
if (height) this.height = height
if (x) this.x = x
if (y) this.y = y
}
setAttributes() {
this.text = {
...this.text,
value: this.text.value || '小标题',
x: this.x,
y: this.y - this.height/2 + 15
}
}
getNodeStyle() {
const style = super.getNodeStyle();
return {
...style,
stroke: nodeConfig.stroke,
fill: 'transparent'
};
}
getDefaultAnchor() {
return [];
}
}
class LaneView extends GroupNode {
getOperateIcon() {
const { model } = this.props;
const {isSelected} = model
if (!isSelected) {
return null
}
return [
this.addAboveIcon(),
this.addBelowIcon(),
this.deleteIcon()
]
}
addAboveIcon() {
const {x,y,width, height, id} = this.props.model
return h('g', {
cursor: 'pointer',
onClick: () => {
const groupId = this.props.graphModel.group.nodeGroupMap.get(id)
if (groupId) {
const groupModel = this.props.graphModel.getNodeModelById(groupId)
groupModel.addChildAbove({ x, y, width, height})
}
}
}, [
h('rect', {
height: 20,
width: 7,
strokeWidth: 1,
fill: '#fff',
stroke: '#000',
strokeDasharray: '3 3',
x: x + width /2 + 15,
y: y - height/2 + 3
}),
h('rect', {
height: 20,
width: 10,
strokeWidth: 1,
fill: '#fff',
stroke: '#000',
x: x + width /2 + 22,
y: y - height/2 + 3
})
])
}
addBelowIcon() {
const {x,y,width, height, id} = this.props.model
return h('g', {
cursor: 'pointer',
onClick: () => {
const groupId = this.props.graphModel.group.nodeGroupMap.get(id)
if (groupId) {
const groupModel = this.props.graphModel.getNodeModelById(groupId)
groupModel.addChildBelow({ x, y, width, height})
}
}
}, [
h('rect', {
height: 20,
width: 7,
strokeWidth: 1,
fill: '#fff',
stroke: '#000',
strokeDasharray: '3 3',
x: x + width /2 + 25,
y: y - height/2 + 32
}),
h('rect', {
height: 20,
width: 10,
strokeWidth: 1,
fill: '#fff',
stroke: '#000',
x: x + width /2 + 15,
y: y - height/2 + 32
})
]) }
deleteIcon() {
const { x,y, width, height, id } = this.props.model;
return h('g',
{
cursor: 'pointer',
onClick: () => {
const groupId = this.props.graphModel.group.nodeGroupMap.get(id)
if (groupId) {
const groupModel = this.props.graphModel.getNodeModelById(groupId)
groupModel.deleteChild(id, groupId)
}
},
},
[
h('rect', {
height: 20,
width: 20,
rx: 2,
ry: 2,
strokeWidth: 1,
fill: 'transparent',
stroke: 'transparent',
x: x + width / 2 + 14,
y: y - height / 2 + 60,
}),
h('svg', {
transform: 'translate(1.000000, 1.000000)',
// fill: '#3C96FE',
x: x + width / 2 + 14,
y: y - height / 2 + 60,
width: 20,
height: 20,
},
[
h('path', {
'pointer-events': 'none',
d: 'M15.3,1.4 L12.6,1.4 L12.6,0 L5.4,0 L5.4,1.4 L0,1.4 L0,2.8 L2,2.8 L2,17.3 C2,17.6865993 2.31340068,18 2.7,18 L15.3,18 C15.6865993,18 16,17.6865993 16,17.3 L16,2.8 L18,2.8 L18,1.4 L15.3,1.4 Z M14.6,16.6 L3.4,16.6 L3.4,2.8 L14.6,2.8 L14.6,16.6 Z',
}),
h('path', {
'pointer-events': 'none',
d: 'M6,5.4 L7.4,5.4 L7.4,14.4 L6,14.4 L6,5.4 Z M10.6,5.4 L12,5.4 L12,14.4 L10.6,14.4 L10.6,5.4 Z',
}),
]),
]); }
getResizeShape() {
const { model, graphModel } = this.props;
const {id, x, y, width, height, isSelected, stroke, strokeWidth} = model
const style = model.getNodeStyle()
// 标题区域 rect
const foldRectAttrs = {
...style,
x: x - width / 2,
y: y - height / 2,
width: width,
height: 30,
zIndex: 10,
}
const left = x - width / 2;
const top = y - height / 2;
const minW = 60;
const minH = 40;
const strokeRect = h('rect', {
x: left, y: top, width, height,
fill: 'none',
stroke: model.properties.strokeHover ? '#0084ff' : '#666', // 悬停时边框加粗
strokeWidth: model.properties.strokeHover ? 4 : 2,
style: { pointerEvents: 'stroke' },
attrs: { 'pointer-events': 'stroke' },
cursor: 'nwse-resize',
onMouseEnter: () => model.setProperty('strokeHover', true),
onMouseLeave: () => model.setProperty('strokeHover', false),
onMouseDown: (ev) => {
ev.stopPropagation();
const startX = ev.clientX;
const startY = ev.clientY;
// 节点初始几何数据
const startW = model.width;
const startH = model.height;
const startNodeX = model.x;
const startNodeY = model.y;
graphModel.eventCenter.emit('node:resize-start', {
e: ev,
preData: { id, type: 'lane', x: startNodeX, y: startNodeY, width: startW, height: startH },
model,
});
const onMouseMove = (moveEv) => {
const dx = moveEv.clientX - startX;
const dy = moveEv.clientY - startY;
const newW = Math.max(20, Math.round(startW + dx));
const newH = Math.max(10, Math.round(startH + dy));
// 更新尺寸
if (typeof model.setWidth === 'function') {
model.setWidth(newW);
} else {
model.width = newW;
}
if (typeof model.setHeight === 'function') {
model.setHeight(newH);
} else {
model.height = newH;
}
// 通知 LogicFlow 更新
graphModel.eventCenter.emit('node:update', { node: model });
// ⚡派发 node:resize,带上 x,y
graphModel.eventCenter.emit('node:resize', {
e: moveEv,
preData: {
x: startNodeX,
y: startNodeY,
width: startW,
height: startH,
type: 'lane',
id,
},
data: {
x: model.x,
y: model.y,
width: newW,
height: newH,
type: 'lane',
id,
},
model,
deltaX: dx,
deltaY: dy
});
};
const onMouseUp = (upEv) => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
graphModel.eventCenter.emit('node:resize-end', {
e: upEv,
preData: {
x: startNodeX,
y: startNodeY,
width: startW,
height: startH,
type: 'lane',
id,
},
data: {
x: model.x,
y: model.y,
width: model.width,
height: model.height,
type: 'lane',
id,
},
model,
});
};
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('mouseup', onMouseUp);
}
});
return h('g', {
}, [
h('rect', {...foldRectAttrs}),
super.getResizeShape(),
this.getOperateIcon(),
strokeRect
])
}
}
const LaneNode = {
type: 'lane',
view: LaneView,
model: LaneModel
}
export default LaneNode
// nodeConfig.js
export default {
stroke: '#666',
strokeWidth: 1,
strokeWidthBold: 2,
strokeWidthBoldL2: 3,
red: '#FC5662',
}
// index.vue关键代码
lf.renderRawData(lfData)
lf.on('node:dnd-add, edge:add', ({data}) => {
lf.setProperties(data.id, containerRef.value || {})
const {x,y,type, id} = data
if (type === 'pool') {
const poolModel = lf.getNodeModelById(id)
const {width, height} = poolModel
const {id:laneId} = lf.addNode({
type: 'lane',
properties: {
nodeSize: {
width: width,
height: height
},
x: x,
y: y
},
x: x,
y: y,
})
poolModel.addChild(laneId)
}
})
lf.on('node:resize', ({preData: oldNodeSize, data: newNodeSize}) => {
console.log('node:resize', oldNodeSize, newNodeSize)
console.log('node:resize', oldNodeSize, newNodeSize)
const {id, type} = oldNodeSize
// const deltaHeight = newNodeSize.height - oldNodeSize.height
const deltaWidth = newNodeSize.width - oldNodeSize.width
let resizeDir = 'below'
if (deltaWidth > 0 && (newNodeSize.x - oldNodeSize.x) < 0) {
resizeDir = 'above'
} else if (deltaWidth < 0 && (newNodeSize.x - oldNodeSize.x) > 0){
resizeDir = 'above'
}
if (type === 'pool') {
// 泳池缩放,泳道一起调整
// lf.getNodeModelById(id).resizeChildren({resizeDir, deltaHeight})
lf.getNodeModelById(id).resizeChildren({resizeDir, deltaWidth})
} else if (type === 'lane') {
// 泳道缩放, 调整泳池
const groupId = lf.extension.group.nodeGroupMap.get(id)
if(groupId) {
lf.getNodeModelById(groupId).resize(id, newNodeSize)
}
}
})
logicflow/core版本
2.1.1
logicflow/extension版本
2.1.2
logicflow/engine版本
No response
浏览器&环境
Chrome
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working