vue项目里引用 gojs 流程图

    技术2022-07-11  102

    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: "", //设置统一的宽高 the default port: if no spot on link data, use closest side 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), // the link shape $(go.Shape, // the arrowhead { 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, // this is changed to 'white' in the showPorts function desiredSize: new go.Size(8, 8), alignment: spot, alignmentFocus: spot, // align the port on the main Shape portId: name, // declare this object to be a 'port' fromSpot: spot, toSpot: spot, // declare where links may connect at this port fromLinkable: output, toLinkable: input, // declare whether the user may draw links to/from here cursor: 'pointer' // show a different cursor to indicate potential link point }) },

    四个命名端口,每侧一个:参数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 [ // The Node.location comes from the "loc" property of the node data, // converted by the Point.parse static method. // If the Node.location is changed, it updates the "loc" property of the node data, // converting back using the Point.stringify static method. new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify), {// the Node.location is at the center of each node locationSpot: go.Spot.Center, //isShadowed: true, //shadowColor: "#888", // handle mouse enter/leave events to show/hide the ports 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, { // have mouse wheel events zoom in and out instead of scroll up and down/具有鼠标滚轮事件放大和缩小,而不是上下滚动 "toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom, // initialAutoScale: go.Diagram.Uniform,加上之后定义的出入口就失效了 // "linkingTool.direction": go.LinkingTool.ForwardsOnly, initialDocumentSpot:go.Spot.Top, initialContentAlignment: go.Spot.Center,// 居中显示 // layout: $(go.TreeLayout,{ isInitial: false, isOngoing: true, angle:90 },), 'undoManager.isEnabled': true, 支持 Ctrl-Z 和 Ctrl-Y 操作 // Model ChangedEvents get passed up to component users '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: "", // the default port: if no spot on link data, use closest side fromLinkable: true, toLinkable: true, cursor: "pointer", // fromSpot:go.Spot.TopCenter,toSpot:go.Spot.BottomCenter, }, 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')) ), ), // three named ports, one on each side except the top, all output only 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: "", //设置统一的宽高 the default port: if no spot on link data, use closest side 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')) ), ), // four named ports, one on each side: 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: "", // the default port: if no spot on link data, use closest side 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')) ), ), // three named ports, one on each side except the bottom, all input only: 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, // must name or refer to the DIV HTML element { 'animationManager.duration': 800, // slightly longer than default (600ms) animation nodeTemplateMap: myDiagram.nodeTemplateMap, // share the templates used by myDiagram // nodeTemplate: myDiagram.nodeTemplate, // share the templates used by myDiagram model: new go.GraphLinksModel([ // specify the contents of the Palette {"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, // this is changed to 'white' in the showPorts function desiredSize: new go.Size(8, 8), alignment: spot, alignmentFocus: spot, // align the port on the main Shape portId: name, // declare this object to be a 'port' fromSpot: spot, toSpot: spot, // declare where links may connect at this port fromLinkable: output, toLinkable: input, // declare whether the user may draw links to/from here cursor: 'pointer' // show a different cursor to indicate potential link point }) }, nodeStyle () { return [ // The Node.location comes from the "loc" property of the node data, // converted by the Point.parse static method. // If the Node.location is changed, it updates the "loc" property of the node data, // converting back using the Point.stringify static method. new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify), {// the Node.location is at the center of each node locationSpot: go.Spot.Center, //isShadowed: true, //shadowColor: "#888", // handle mouse enter/leave events to show/hide the ports 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) { // No GoJS transaction permitted when replacing Diagram.model. 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 is very general but very inefficient. // It would be better to modify the diagramData data by calling // Model.setDataProperty or Model.addNodeData, et al. 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 { // diagramData: {}, 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', // 'text': 'up or timer', '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, // used by addNode counter2: 4 // used by modifyStuff } }, 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: { // get access to the GoJS Model of the GoJS Diagram model: function () { return this.$refs.diag.model() }, // tell the GoJS Diagram to update based on the arbitrarily modified model data updateDiagramFromData: function () { this.$refs.diag.updateDiagramFromData() }, // this event listener is declared on the <diagram> modelChanged: function (e) { if (e.isTransactionFinished) { // show the model data in the page's TextArea 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) { }, // Here we modify the GoJS Diagram's Model using its methods, // which can be much more efficient than modifying some memory and asking // the GoJS Diagram to find differences and update accordingly. // Undo and Redo will work as expected. 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') // also manipulate the Diagram by changing its Diagram.selection collection let diagram = this.$refs.diag.diagram diagram.select(diagram.findNodeForData(data)) }, // Here we modify VUE's view model directly, and // then ask the GoJS Diagram to update everything from the data. // This is less efficient than calling the appropriate GoJS Model methods. // NOTE: Undo will not be able to restore all of the state properly!! modifyStuff: function () { let data = this.diagramData data.nodeDataArray[0].color = 'red' // Note here that because we do not have the GoJS Model, // we cannot find out what values would be unique keys, for reference by the link data. 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>
    Processed: 0.011, SQL: 9