vue项目里引用 gojs 流程图

vue+gojs 流程图

要实现的需求

流程图,支持字体图标,颜色,可连接线,点击时右侧展示相关的详细信息
vue项目里引用 gojs 流程图_第1张图片调研了多种可拖拽流程图的技术,如: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 
        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>

你可能感兴趣的:(vue,流程图,GoJS,vue.js)