vue+gojs 流程图
要实现的需求
流程图,支持字体图标,颜色,可连接线,点击时右侧展示相关的详细信息 调研了多种可拖拽流程图的技术,如:bpmn.js,gojs等,由于bpmn-js功能冗余,GOJS相对于更加轻量级,最终选用GOJS开发此功能
引入gojs
1.安装 npm install gojs --save 2.在main.js中引入 import gojs from ‘gojs’ Vue.prototype.go = gojs
文字区块
使用TextBlock类显示文本。 设置TextBlock.text属性是显示文本字符串的唯一方法。因为TextBlock继承自GraphObject,所以某些GraphObject属性会影响文本
字体和颜色
文本的大小和样式外观由TextBlock.font指定。该值可以是任何CSS字体说明符字符串。
myDiagram
.nodeTemplateMap
.add('Pending',
$(go
.Node
, 'Spot', this.nodeStyle(),
$(go
.Panel
, 'Auto',
$(go
.Shape
, 'RoundedRectangle',
{ width
: 100,height
: 40, fill
: '#17c2b9', stroke
: null, portId
: "",
fromLinkable
: true, toLinkable
: true, cursor
: "pointer", },
new go.Binding("location", "loc", go
.Point
.parse
)),
$(go
.Panel
, "Horizontal", { margin
: 5 },
$(go
.TextBlock
,"Pending", { text
: '\uf030', font
: '10pt FontAwesome' ,textAlign
:'left',verticalAlignment
: go
.Spot
.Left
,}),
$(go
.TextBlock
,"Pending",
{
textAlign
:'right',
font
: 'bold 11pt Helvetica, Arial, sans-serif',
stroke
: '#fff',
margin
:5,
maxSize
: new go.Size(100, NaN),
wrap
: go
.TextBlock
.WrapFit
,
editable
: true
},
new go.Binding('text'))
),
),
))
字体图标
首先,在创建图表之前,请确保该字体已加载到页面中,在main.js中引入
import './assets/fontAwesome/css/font-awesome.min.css'
$(go
.TextBlock
,"Pending",{ text
: '\uf030', font
: '10pt FontAwesome',}),
箭头
许多链接确实希望通过使用箭头来指示方向性。 GoJS使创建通用箭头变得容易:只需添加Shape并设置其Shape.toArrow属性即可。设置该属性将自动分配一个几何到Shape.geometry 并且使得箭头位于连杆的头部并以正确的方向指向将设置其他属性。
diagram
.nodeTemplate
=
$(go
.Node
, "Auto",
new go.Binding("location", "loc", go
.Point
.parse
),
$(go
.Shape
, "RoundedRectangle", { fill
: "lightgray" }),
$(go
.TextBlock
, { margin
: 5 },
new go.Binding("text", "key"))
);
diagram
.linkTemplate
=
$(go
.Link
,
$(go
.Shape
),
$(go
.Shape
,
{ toArrow
: "OpenTriangle", fill
: null })
);
var nodeDataArray
= [
{ key
: "Alpha", loc
: "0 0" },
{ key
: "Beta", loc
: "100 50" }
];
var linkDataArray
= [
{ from: "Alpha", to
: "Beta" }
];
diagram
.model
= new go.GraphLinksModel(nodeDataArray
, linkDataArray
);
hover效果显示可连接点
makePort (name
, spot
, output
, input
) {
return $(go
.Shape
, 'Circle',
{
fill
: 'transparent',
stroke
: null,
desiredSize
: new go.Size(8, 8),
alignment
: spot
,
alignmentFocus
: spot
,
portId
: name
,
fromSpot
: spot
,
toSpot
: spot
,
fromLinkable
: output
,
toLinkable
: input
,
cursor
: 'pointer'
})
},
四个命名端口,每侧一个:参数output是输出,input是输入
this.makePort('T', go
.Spot
.Top
, false, true),
this.makePort('L', go
.Spot
.Left
, true, true),
this.makePort('R', go
.Spot
.Right
, true, true),
this.makePort('B', go
.Spot
.Bottom
, true, false),
自动布局
GoJS提供了几种自动布局,包括: GridLayout(栅格布局) TreeLayout(树形布局) ForceDirectedLayout(力导向布局) LayeredDigraphLayout(分层有向图布局) CircularLayout(圆形布局) 当自动布局生效后,用户进行新增节点或者删除节点,会再次触发自动布局效果,原有坐标均被重置.也就是说,只要用户有新增节点的操作,前面的修改会全部重置 解决方案: 将isOngoing设置为false,以防止添加或删除部件等操作使此布局无效。默认值为true。
layout
: $(go
.TreeLayout
,{ isInitial
: false, isOngoing
: true, angle
:90 },),
具体可查看官网地址https://gojs.net/latest/api/symbols/Layout.html
保存格式
图表模型以JSON格式保存
{ "class": "go.GraphLinksModel",
"nodeDataArray": [
{"category":"Command",
"title":"tsfsfsfsfsfsf",
"text":"源码构建",
"key":-3,
"loc":"-109.79687500000006 -248.24999999999994"
}
],
"linkDataArray":[{"from":-2, "to":-3, "curviness":-20, "points":[ -202.90625,-353.06227569580085,-202.90625,-343.06227569580085,-202.90625, -308.875,-109.796875,-308.875,-109.796875,-274.6877243041992,-109.796875,-264.68772 43041992
]}
]}
曲线,弯曲度
使用Link类可实现节点之间的可视关系。 默认情况下会产生一条轻微的曲线。 您可以通过设置Link.curviness属性来控制其弯曲程度。
$(go
.Link
,
{ curve
: go
.Link
.Bezier
,
},
$(go
.Shape
),
$(go
.Shape
, { toArrow
: "Standard" })
);
制定规则
防止两个节点之间出现多条连线
nodeStyle () {
return [
new go.Binding('location', 'loc', go
.Point
.parse
).makeTwoWay(go
.Point
.stringify
),
{
locationSpot
: go
.Spot
.Center
,
mouseEnter
: (e
, obj
) => {
this.showPorts(obj
.part
, true)
},
mouseLeave
: (e
, obj
) => {
this.showPorts(obj
.part
, false)
}
},
{
linkValidation
: function (fromNode
, fromPort
, toNode
, toPort
) {
return fromNode
.findLinksOutOf().all(function (link
) {
console
.log(link
.toNode
!== toNode
)
return link
.toNode
!== toNode
;
})
},
},
]
},
附上代码
子组件
<template
>
<div style
='width:100%; white-space:nowrap;'>
<span style
='border: 1px solid gray;display: inline-block; vertical-align: top; width:150px;'>
<div ref
='myPaletteDiv' style
='height: 500px;'>1111</div
>
</span
>
<span style
='border: 1px solid gray;display: inline-block; vertical-align: top; width:40%;'>
<div ref
='myDiagramDiv' style
='height: 500px'></div
>
</span
>
</div
>
</template
>
<script
>
let $
= go
.GraphObject
.make
export default {
name
: '',
props
: ['modelData'],
data () {
return {
diagram
: null
}
},
mounted () {
let self
= this
let myDiagram
=
$(go
.Diagram
, this.$refs
.myDiagramDiv
,
{
"toolManager.mouseWheelBehavior": go
.ToolManager
.WheelZoom
,
initialDocumentSpot
:go
.Spot
.Top
,
initialContentAlignment
: go
.Spot
.Center
,
'undoManager.isEnabled': true,
'ModelChanged': function (e
) {
self
.$emit('model-changed', e
)
},
'ChangedSelection': function (e
) {
self
.$emit('changed-selection', e
)
},
'Modified': function (e
) {
self
.$emit('modified', e
)
},
'TextEdited': function (e
) {
self
.$emit('text-edited', e
)
},
allowDrop
: true
})
myDiagram
.nodeTemplateMap
.add('Start',
$(go
.Node
, 'Spot', this.nodeStyle(),
$(go
.Panel
, 'Auto',
$(go
.Shape
, 'RoundedRectangle',
{
width
: 100,height
: 40, fill
: '#98FB98', stroke
: null, portId
: "",
fromLinkable
: true, toLinkable
: true, cursor
: "pointer",
},
new go.Binding("location", "loc", go
.Point
.parse
)),
$(go
.Panel
, "Horizontal",{ margin
: 5 },
$(go
.TextBlock
,"Start",
{ text
: '\uf1c1', font
: '10pt FontAwesome' ,textAlign
:'left',verticalAlignment
: go
.Spot
.Left
,}),
$(go
.TextBlock
,"Start",
{
textAlign
:'right',
font
: 'bold 11pt Helvetica, Arial, sans-serif',
stroke
: '#fff',
margin
:5,
maxSize
: new go.Size(100, NaN),
wrap
: go
.TextBlock
.WrapFit
,
editable
: false
},
new go.Binding('text'))
),
),
this.makePort('L', go
.Spot
.Left
, true, false),
this.makePort('R', go
.Spot
.Right
, true, false),
this.makePort('B', go
.Spot
.Bottom
, true, false),
))
myDiagram
.nodeTemplateMap
.add('Pending',
$(go
.Node
, 'Spot', this.nodeStyle(),
$(go
.Panel
, 'Auto',
$(go
.Shape
, 'RoundedRectangle',
{ width
: 100,height
: 40, fill
: '#17c2b9', stroke
: null, portId
: "",
fromLinkable
: true, toLinkable
: true, cursor
: "pointer", },
new go.Binding("location", "loc", go
.Point
.parse
)),
$(go
.Panel
, "Horizontal", { margin
: 5 },
$(go
.TextBlock
,"Pending", { text
: '\uf030', font
: '10pt FontAwesome' ,textAlign
:'left',verticalAlignment
: go
.Spot
.Left
,}),
$(go
.TextBlock
,"Pending",
{
textAlign
:'right',
font
: 'bold 11pt Helvetica, Arial, sans-serif',
stroke
: '#fff',
margin
:5,
maxSize
: new go.Size(100, NaN),
wrap
: go
.TextBlock
.WrapFit
,
editable
: true
},
new go.Binding('text'))
),
),
this.makePort('T', go
.Spot
.Top
, false, true),
this.makePort('L', go
.Spot
.Left
, true, true),
this.makePort('R', go
.Spot
.Right
, true, true),
this.makePort('B', go
.Spot
.Bottom
, true, false),
))
myDiagram
.nodeTemplateMap
.add('End',
$(go
.Node
, 'Spot', this.nodeStyle(),
$(go
.Panel
, 'Auto',
$(go
.Shape
, 'RoundedRectangle',
{
width
: 100,height
: 40, fill
: '#8e9499', stroke
: null, portId
: "",
fromLinkable
: true, toLinkable
: true, cursor
: "pointer",
},
new go.Binding("location", "loc", go
.Point
.parse
)),
$(go
.Panel
, "Horizontal",
{ margin
: 5 },
$(go
.TextBlock
,"End",
{ text
: '\uf039', font
: '10pt FontAwesome' ,textAlign
:'left',verticalAlignment
: go
.Spot
.Left
,}),
$(go
.TextBlock
,"End",
{
textAlign
:'right',
font
: 'bold 11pt Helvetica, Arial, sans-serif',
stroke
: '#fff',
margin
:5,
maxSize
: new go.Size(100, NaN),
wrap
: go
.TextBlock
.WrapFit
,
editable
: false
},
new go.Binding('text'))
),
),
this.makePort('T', go
.Spot
.Top
, false, true),
this.makePort('L', go
.Spot
.Left
, false, true),
this.makePort('R', go
.Spot
.Right
, false, true),
))
myDiagram
.linkTemplate
=
$(go
.Link
,
$(go
.Shape
,
new go.Binding("stroke", "color"),
new go.Binding("strokeWidth", "width"),
new go.Binding("strokeDashArray", "dash"))
);
let myPalette
=
$(go
.Palette
, this.$refs
.myPaletteDiv
,
{
'animationManager.duration': 800,
nodeTemplateMap
: myDiagram
.nodeTemplateMap
,
model
: new go.GraphLinksModel([
{"key":0, "category":"Start", "loc":"175 0", "text":"开始"},
{"key":1, "category":"Pending", "loc":"175 50", "text":"源码检查"},
{"key":2, "category":"Pending","loc":"175 100", "text":"源码构建"},
{"key":3, "category":"Pending","loc":"175 450", "text":"自动测试"},
{"key":4, "category":"End", "loc":"175 500", "text":"结束"}
])
})
console
.log(myPalette
)
this.diagram
= myDiagram
this.updateModel(this.modelData
)
},
watch
: {
modelData
: function (val
) {
console
.log('watch')
console
.log(val
)
this.updateModel(val
)
}
},
computed
: {},
methods
: {
makePort (name
, spot
, output
, input
) {
return $(go
.Shape
, 'Circle',
{
fill
: 'transparent',
stroke
: null,
desiredSize
: new go.Size(8, 8),
alignment
: spot
,
alignmentFocus
: spot
,
portId
: name
,
fromSpot
: spot
,
toSpot
: spot
,
fromLinkable
: output
,
toLinkable
: input
,
cursor
: 'pointer'
})
},
nodeStyle () {
return [
new go.Binding('location', 'loc', go
.Point
.parse
).makeTwoWay(go
.Point
.stringify
),
{
locationSpot
: go
.Spot
.Center
,
mouseEnter
: (e
, obj
) => {
this.showPorts(obj
.part
, true)
},
mouseLeave
: (e
, obj
) => {
this.showPorts(obj
.part
, false)
}
},
{
linkValidation
: function (fromNode
, fromPort
, toNode
, toPort
) {
return fromNode
.findLinksOutOf().all(function (link
) {
console
.log(link
.toNode
!== toNode
)
return link
.toNode
!== toNode
;
})
},
},
]
},
showPorts (node
, show
) {
let diagram
= node
.diagram
if (!diagram
|| diagram
.isReadOnly
|| !diagram
.allowLink
) return
node
.ports
.each(function (port
) {
port
.stroke
= (show
? 'white' : null)
})
},
model
: function () {
return this.diagram
.model
},
updateModel
: function (val
) {
if (val
instanceof go.Model) {
this.diagram
.model
= val
} else {
let m
= new go.GraphLinksModel()
if (val
) {
for (let p
in val
) {
m
[p
] = val
[p
]
}
}
this.diagram
.model
= m
}
},
updateDiagramFromData
: function () {
this.diagram
.startTransaction()
this.diagram
.updateAllRelationshipsFromData()
this.diagram
.updateAllTargetBindings()
this.diagram
.commitTransaction('updated')
}
}
}
</script
>
<style
>
</style
>
#### 父组件
<template
>
<div
>
<div
class="div" style
="display:flex;">
<div
class="left" style
="width: 60%;text-align: left;">
<diagram ref
='diag' :model
-data
='diagramData' @model
-changed
='modelChanged' @changed
-selection
='changedSelection' @text
-edited
="textEdited" @modified
="modified" style
='width:100%; height:500px'></diagram
>
</div
>
<!-- <button @click
='addNode'>Add Child to Gamma
</button
>
<button @click
='modifyStuff'>Modify view model data without undo
</button
> -->
<div
class="right" style
="float:right">
<br
/>Current Node
:
<input v
-model
.lazy
='currentNodeText' :disabled
='currentNode === null'/>
</div
>
</div
>
<br
/>The saved GoJS Model
:
<!--<textarea style
='width:100%;height:250px'>{{ savedModelText
}}</textarea
>-->
<textarea style
='width:100%;height:200px' v
-model
="savedModelText"></textarea
>
</div
>
</template
>
<script
>
import diagram
from '../components/GoDiagramWorkflow'
export default {
name
: '',
components
: {
diagram
},
data () {
return {
diagramData2
: {
'class': 'go.GraphLinksModel',
'linkFromPortIdProperty': 'fromPort',
'linkToPortIdProperty': 'toPort',
'nodeDataArray': [],
'linkDataArray': []
},
diagramData
: {
'class': 'go.GraphLinksModel',
'linkFromPortIdProperty': 'fromPort',
'linkToPortIdProperty': 'toPort',
'nodeDataArray': [
{
'category': 'Start',
'text': '开始',
'key': 0,
'loc': '-202.90624999999994 -369.4999999999998'
},
{
'category': 'Pending',
'title': 'tsfsfsfsfsfsf',
'text': '源码构建',
'key': 2,
'loc': '-109.79687500000006 -248.24999999999994'
}
],
'linkDataArray': [{
'from': 0,
'to': 2,
'fromPort': 'B',
'toPort': 'T',
'curviness': -20,
'points': [-202.90625, -353.06227569580085, -202.90625, -343.06227569580085, -202.90625, -308.875, -109.796875, -308.875, -109.796875, -274.6877243041992, -109.796875, -264.6877243041992]
}]
},
currentNode
: null,
savedModelText
: '',
counter
: 1,
counter2
: 4
}
},
mounted () {
},
computed
: {
currentNodeText
: {
get: function () {
let node
= this.currentNode
console
.log(window
.go
.Node
)
if (node
instanceof window.go.Node) {
console
.log(node
.data
,)
return node
.data
.text
} else {
return ''
}
},
set: function (val
) {
let node
= this.currentNode
if (node
instanceof window.go.Node) {
let model
= this.model()
model
.startTransaction()
model
.setDataProperty(node
.data
, 'text', val
)
model
.commitTransaction('edited text')
}
}
}
},
methods
: {
model
: function () {
return this.$refs
.diag
.model()
},
updateDiagramFromData
: function () {
this.$refs
.diag
.updateDiagramFromData()
},
modelChanged
: function (e
) {
if (e
.isTransactionFinished
) {
this.savedModelText
= e
.model
.toJson()
}
},
changedSelection
: function (e
) {
let node
= e
.diagram
.selection
.first()
if (node
instanceof window.go.Node) {
this.currentNode
= node
this.currentNodeText
= node
.data
.text
} else {
this.currentNode
= null
this.currentNodeText
= ''
}
},
textEdited
: function (e
) {
let data
= this.diagramData
let nodeDataArray
= data
.nodeDataArray
let len
= nodeDataArray
.length
for (let i
= 0; i
< len
; i
++) {
nodeDataArray
[i
]['text'] = nodeDataArray
[i
]['text'].replace(/:/g, ':')
console
.log(nodeDataArray
[i
]['text'])
}
this.updateDiagramFromData()
},
modified
: function (e
) {
},
addNode
: function () {
let model
= this.model()
model
.startTransaction()
model
.setDataProperty(model
.findNodeDataForKey(4), 'color', 'purple')
let data
= { text
: 'NEW ' + this.counter
++, color
: 'yellow' }
model
.addNodeData(data
)
model
.addLinkData({ from: 3, to
: model
.getKeyForNodeData(data
) })
model
.commitTransaction('added Node and Link')
let diagram
= this.$refs
.diag
.diagram
diagram
.select(diagram
.findNodeForData(data
))
},
modifyStuff
: function () {
let data
= this.diagramData
data
.nodeDataArray
[0].color
= 'red'
data
.nodeDataArray
.push({ key
: ++this.counter2
, text
: this.counter2
.toString(), color
: 'orange' })
data
.linkDataArray
.push({ from: 2, to
: this.counter2
})
this.updateDiagramFromData()
}
},
mounted(){
}
}
</script
>
<style
>
</style
>