22 Dec 2017

GOJS+vue2的前端工作流组件

简介
GOJS是一个能够让我们很容易的实现基于html5浏览器绘制具有交互性的图形图表的JavaScript框架。
我们需要做的就是创建图形对象、构建数据模型、设置属性、绑定数据模型、使用工具类添加行为即可创建出具有丰富交互性能的各种图表
官网

引入
添加依赖 npm install gojs --save

// 引入gojs
import Vue from 'vue'
import go from 'gojs';
// 全局使用 this.go
Vue.prototype.go = go;
Vue.prototype.flow = go.GraphObject.make; //核心

去除水印

// 在go.js混淆代码中尝试去掉水印:
//a.ax = d[u.Da("7eba17a4ca3b1a8346")][u.Da("78a118b7")](d, u.wl, 4, 4);
a.ax= function(){return true;} 

1. 渲染图层

<template>  
    <div id="mgflowDiv" style="width:1000px; height:500px; background-color: #DAE4E4;"></div>
</template>

2. 引入文件

import go from 'gojs';
const mgflow = go.GraphObject.make; //核心

3. 创建模型图

 const myDiagram = mgflow(go.Diagram, this.$el, {
     initialContentAlignment: go.Spot.Center,
     // 模型图的中心位置所在坐标
     "undoManager.isEnabled": true
     // 启用Ctrl-Z撤销和Ctrl-Y重做快捷键
 });

4. 基本数据节点Model对象

最基本的

 var myModel = mgflow(go.Model);
 myModel.nodeDataArray = [
   { key: "Alpha" },
   { key: "Beta" },
   { key: "Gamma" }
 ];
 //将模型数据绑定到模型图上
 myDiagram.model = myModel;

动态连线图

var model = mgflow(go.GraphLinksModel);
model.nodeDataArray =[  { key: "A" },  { key: "B" },  { key: "C" }];
model.linkDataArray =[  { from: "A", to: "B" },  { from: "B", to: "C" }];
myDiagram.model = model;

树形图模型

var model = mgflow(go.TreeModel);
    model.nodeDataArray = [
            { key: "1", name: "Don Meow", source: "cat1.png" },
            { key: "2", parent: "1", name: "Demeter", source: "cat2.png" },
            { key: "3", parent: "1", name: "Copricat", source: "cat3.png" },
            { key: "4", parent: "3", name: "Jellylorum", source: "cat4.png" },
            { key: "5", parent: "3", name: "Alonzo", source: "cat5.png" },
            { key: "6", parent: "2", name: "Munkustrap", source: "cat6.png" }
        ];
myDiagram.model = model;

5. 连接线

  // 默认TreeLayout是从左到右的,修改为从上到下的代码
  myDiagram.layout =mgflow(go.TreeLayout, { angle: 90, layerSpacing: 35 });

6. 节点Node

 // // Shape:Rectangle(矩形)、RoundedRectangle(圆角矩形),Ellipse(椭圆形),Triangle(三角形),Diamond(菱形),Circle(圆形)等
 // // TextBlock:文本域(可编辑)
 // // Picture:图片
 // // Panel:容器来保存其他Node的集合 
 // myDiagram.nodeTemplate =mgflow(go.Node,mgflow(go.TextBlock,new go.Binding("text","key")));
 // myDiagram.nodeTemplate =
 //   mgflow(go.Node, "Vertical",{ /* 参数 */ },
 //     // 绑定节点坐标Node.location为Node.data.loc的值 Model对象可以通过Node.data.loc 获取和设置Node.location(修改节点坐标)
 //     new go.Binding("location", "loc"),
 //     //Shape上面有个 TextBlock
 //     mgflow(go.Shape,"RoundedRectangle",{ /* 宽高颜色等等*/ },
 //       // 绑定 Shape.figure属性为Node.data.fig的值,Model对象可以通过Node.data.fig 获取和设置Shape.figure(修改形状)
 //       new go.Binding("figure", "fig")),
 //     mgflow(go.TextBlock,"默认文本",{ /* 字体样式 */ },
 //       // 绑定TextBlock.text属性为Node.data.key的值,Model对象可以通过Node.data.key获取和设置TextBlock.text(修改文本)
 //       new go.Binding("text", "key"))
 //   );
 myDiagram.nodeTemplate =
     // 布局方式,垂直分布:Vertical,横向布局:Horizontal
     mgflow(go.Node, "Horizontal", { background: "#44CCFF" },
         mgflow(go.Picture, { margin: 10, width: 50, height: 50, background: "red" },
             new go.Binding("source")),
         mgflow(go.TextBlock, "Default Text", { margin: 12, stroke: "white", font: "bold 16px sans-serif" },
             new go.Binding("text", "name"))
     );

7. 完整代码

<template>
    <!--  渲染图层 -->
    <div id="mgflowDiv" style="width:1000px; height:500px; background-color: #DAE4E4;"></div>
</template>
<script>
import go from 'gojs';
const mgflow = go.GraphObject.make; //核心
export default {
    name: 'MyFlow',
    data() {
        return {}
    },
    methods: {
        aaa(data) {}
    },
    mounted() {
        //创建模型图
        const myDiagram = mgflow(go.Diagram, this.$el, {
            initialContentAlignment: go.Spot.Center, 
            "undoManager.isEnabled": true
        });
        // 默认TreeLayout是从左到右的,修改为从上到下的代码
        myDiagram.layout =
            mgflow(go.TreeLayout, { angle: 90, layerSpacing: 35 });

        myDiagram.nodeTemplate =
            // 布局方式,垂直分布:Vertical,横向布局:Horizontal
            mgflow(go.Node, "Horizontal", { background: "#44CCFF" },
                mgflow(go.Picture, { margin: 10, width: 50, height: 50, background: "red" },
                    new go.Binding("source")),
                mgflow(go.TextBlock, "Default Text", { margin: 12, stroke: "white", font: "bold 16px sans-serif" },
                    new go.Binding("text", "name"))
            );
        var model = mgflow(go.TreeModel);
        model.nodeDataArray = [
            { key: "1", name: "Don Meow", source: "cat1.png" },
            { key: "2", parent: "1", name: "Demeter", source: "cat2.png" },
            { key: "3", parent: "1", name: "Copricat", source: "cat3.png" },
            { key: "4", parent: "3", name: "Jellylorum", source: "cat4.png" },
            { key: "5", parent: "3", name: "Alonzo", source: "cat5.png" },
            { key: "6", parent: "2", name: "Munkustrap", source: "cat6.png" }
        ];
        myDiagram.model = model;
    }
};
</script>
<style>
</style>

8. 流程图组件

<template>
    <div>
        <div style="width:100%; white-space:nowrap;">
            <span style="display: inline-block; vertical-align: top; width:100px">
      <div id="myPaletteDIV" style="border: solid 1px black; height: 650px"></div>
    </span>
      <span style="display: inline-block; vertical-align: top; width:80%">
      <div id="myDiagramDiv" style="border: solid 1px black; height: 650px"></div>
    </span>
        </div>
        <button id="SaveButton" @click="save()">Save</button>
        <button @click="load()">Load</button>
        <button @click="makeSVG()">makeSVG</button>
        <button class="enlarge">+</button>
        <button class="narrow">-</button>
        <div id="SVGArea"></div>
        <div id="contextMenu">
            <ul>
                <li id="cut" onclick="cxcommand(event)"><a href="#" target="_self">Cut</a></li>
                <li id="copy" onclick="cxcommand(event)"><a href="#" target="_self">Copy</a></li>
                <li id="paste" onclick="cxcommand(event)"><a href="#" target="_self">Paste</a></li>
                <li id="delete" onclick="cxcommand(event)"><a href="#" target="_self">Delete</a></li>
                <li id="color" class="hasSubMenu"><a href="#" target="_self">Color</a>
                    <ul class="subMenu" id="colorSubMenu">
                        <li style="background: crimson;" onclick="cxcommand(event, 'color')"><a href="#" target="_self">Red</a></li>
                        <li style="background: chartreuse;" onclick="cxcommand(event, 'color')"><a href="#" target="_self">Green</a></li>
                        <li style="background: aquamarine;" onclick="cxcommand(event, 'color')"><a href="#" target="_self">Blue</a></li>
                        <li style="background: gold;" onclick="cxcommand(event, 'color')"><a href="#" target="_self">Yellow</a></li>
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</template>
<script>
import Vue from 'vue'
import go from 'gojs';
Vue.prototype.go = go;
Vue.prototype.flow = go.GraphObject.make; //核心

var lightText = 'whitesmoke';
export default {
    name: 'MyFlow',
    props: {},
    data() {
        return {
            myPalette: Object,
            myDiagram: Object,
            myContextMenu: Object,
            data: {
                "class": "go.GraphLinksModel",
                "linkFromPortIdProperty": "fromPort",
                "linkToPortIdProperty": "toPort",
                "nodeDataArray": [
                    { "key": -1, "category": "Start", "loc": "175 0", "text": "Start" },
                    { "key": 0, "loc": "175 100", "text": "if/else", "figure": "Diamond" },
                    { "key": 1, "loc": "10 200", "text": "text1" },
                    { "key": 2, "loc": "175 200", "text": "text2" },
                    { "key": 3, "loc": "357 200", "text": "text3" },
                    { "key": 4, "loc": "175 400", "text": "text4" },
                    { "key": -2, "category": "End", "loc": "175 500", "text": "End!" }
                ],
                "linkDataArray": [
                    { "from": 3, "to": 4, "fromPort": "B", "toPort": "T" },
                    { "from": 5, "to": 4, "fromPort": "B", "toPort": "T" },
                    { "from": -1, "to": -3, "fromPort": "B", "toPort": "T" },
                    { "from": -3, "to": 0, "fromPort": "L", "toPort": "T", "visible": true, "text": "选择1" },
                    { "from": -3, "to": 5, "fromPort": "R", "toPort": "T", "visible": true, "text": "选择3" },
                    { "from": -3, "to": 3, "fromPort": "B", "toPort": "T", "visible": true, "text": "选择2" },
                    { "from": 4, "to": -2, "fromPort": "B", "toPort": "T" },
                    { "from": -1, "to": 0, "fromPort": "B", "toPort": "T" },
                    { "from": 0, "to": 1, "fromPort": "L", "toPort": "T", "visible": true, "text": "选择1" },
                    { "from": 0, "to": 3, "fromPort": "R", "toPort": "T", "visible": true, "text": "选择3" },
                    { "from": 0, "to": 2, "fromPort": "B", "toPort": "T", "visible": true, "text": "选择2" },
                    { "from": 1, "to": 4, "fromPort": "B", "toPort": "T", },
                    { "from": 2, "to": 4, "fromPort": "B", "toPort": "T" }
                ]
            }
        }
    },
    methods: {
        initDiagram() {
            this.myDiagram = this.flow(this.go.Diagram, 'myDiagramDiv', {
                initialContentAlignment: this.go.Spot.Center, // 模型图的中心位置所在坐标
                allowDrop: true, //接受从 Palette 中删除的内容
                "LinkDrawn": this.showLinkLabel, // this DiagramEvent listener is defined below
                "LinkRelinked": this.showLinkLabel,
                scrollsPageOnFocus: false,
                "undoManager.isEnabled": true // 启用Ctrl-Z撤销和Ctrl-Y重做快捷键
            });
        },
        initPalette() {
            this.myPalette = this.flow(this.go.Palette, 'myPaletteDIV', {
                scrollsPageOnFocus: false,
                // 分享 myDiagram 的模板
                nodeTemplateMap: this.myDiagram.nodeTemplateMap,
                // 指定 myPalette 显示的内容
                model: new this.go.GraphLinksModel([
                    { category: "Start", text: "Start" },
                    { text: "Step" },
                    { text: "???", figure: "Diamond" },
                    { category: "End", text: "End" },
                    { category: "Comment", text: "Comment" }
                ])
            });
        },
        makePort(name, spot, output, input) {
            // 指针在上面时的显示
            return this.flow(this.go.Shape, "Circle", {
                fill: "transparent",
                stroke: null, //  "white" 
                desiredSize: new this.go.Size(8, 8),
                alignment: spot,
                alignmentFocus: spot,
                portId: name,
                fromSpot: spot,
                toSpot: spot,
                fromLinkable: output,
                toLinkable: input,
                cursor: "pointer"
            });
        },
        nodeStyle() {
            let _self = this;
            return [
                // 绑定节点坐标Node.location为Node.data.loc的值,Model对象可以通过Node.data.loc 获取和设置Node.location(修改节点坐标)
                new this.go.Binding("location", "loc", _self.go.Point.parse).makeTwoWay(_self.go.Point.stringify), {
                    locationSpot: _self.go.Spot.Center,
                    //isShadowed: true,
                    //shadowColor: "#888",
                    mouseEnter: function(e, obj) { _self.showPorts(obj.part, true); },
                    mouseLeave: function(e, obj) { _self.showPorts(obj.part, false); }
                }
            ];
        },
        showLinkLabel(e) {
            var label = e.subject.findObject("LABEL");
            if (label !== null) label.visible = (e.subject.fromNode.data.figure === "Diamond");
        },
        // 节点Node
        addNodeTplDefault() {
            this.myDiagram.nodeTemplateMap.add("", // 默认类型
                this.flow(this.go.Node, "Spot", { contextMenu: this.myContextMenu }, this.nodeStyle(),
                    this.flow(this.go.Panel, "Auto",
                        this.flow(this.go.Shape, "Rectangle", { fill: "#00A9C9", stroke: null },
                            new this.go.Binding("figure", "figure")), this.flow(this.go.TextBlock, {
                                font: "bold 11pt Helvetica, Arial, sans-serif",
                                stroke: 'whitesmoke',
                                margin: 8,
                                maxSize: new this.go.Size(300, NaN),
                                wrap: this.go.TextBlock.WrapFit,
                                editable: false // 节点是否可编辑
                            },
                            new this.go.Binding("text").makeTwoWay())
                    ),
                    // four named ports, one on each side:
                    this.makePort("T", this.go.Spot.Top, false, true),
                    this.makePort("L", this.go.Spot.Left, true, true),
                    this.makePort("R", this.go.Spot.Right, true, true),
                    this.makePort("B", this.go.Spot.Bottom, true, false)
                ));
        },
        addNodeTplStart() {
            this.myDiagram.nodeTemplateMap.add("Start",
                this.flow(this.go.Node, "Spot", this.nodeStyle(),
                    this.flow(this.go.Panel, "Auto",
                        this.flow(this.go.Shape, "Circle", { minSize: new this.go.Size(40, 40), fill: "#79C900", stroke: null }),
                        this.flow(this.go.TextBlock, "Start", { font: "bold 11pt Helvetica, Arial, sans-serif", stroke: lightText },
                            new this.go.Binding("text"))
                    ),
                    // three named ports, one on each side except the top, all output only:
                    this.makePort("L", this.go.Spot.Left, true, false),
                    this.makePort("R", this.go.Spot.Right, true, false),
                    this.makePort("B", this.go.Spot.Bottom, true, false)
                ));
        },
        addNodeTplEnd() {
            this.myDiagram.nodeTemplateMap.add("End",
                this.flow(this.go.Node, "Spot", this.nodeStyle(),
                    this.flow(this.go.Panel, "Auto",
                        this.flow(this.go.Shape, "Circle", { minSize: new this.go.Size(40, 40), fill: "#DC3C00", stroke: null }),
                        this.flow(this.go.TextBlock, "End", { font: "bold 11pt Helvetica, Arial, sans-serif", stroke: lightText },
                            new this.go.Binding("text"))
                    ),
                    // three named ports, one on each side except the bottom, all input only:
                    this.makePort("T", this.go.Spot.Top, false, true),
                    this.makePort("L", this.go.Spot.Left, false, true),
                    this.makePort("R", this.go.Spot.Right, false, true)
                ));
        },
        addNodeTplComment() {
            this.myDiagram.nodeTemplateMap.add("Comment",
                this.flow(this.go.Node, "Auto", this.nodeStyle(),
                    this.flow(this.go.Shape, "File", { fill: "#EFFAB4", stroke: null }),
                    this.flow(this.go.TextBlock, {
                            margin: 5,
                            maxSize: new this.go.Size(400, NaN),
                            wrap: this.go.TextBlock.WrapFit,
                            textAlign: "center",
                            editable: true,
                            font: "bold 12pt Helvetica, Arial, sans-serif",
                            stroke: '#454545'
                        },
                        new this.go.Binding("text").makeTwoWay())
                ));
        },
        addNodelinkTpl() {
            this.myDiagram.linkTemplate =
                this.flow(this.go.Link, {
                        routing: this.go.Link.AvoidsNodes,
                        curve: this.go.Link.JumpOver,
                        corner: 5,
                        toShortLength: 4,
                        relinkableFrom: true,
                        relinkableTo: true,
                        reshapable: true,
                        resegmentable: true,
                        //selectable: false,
                        mouseEnter: function(e, link) { link.findObject("HIGHLIGHT").stroke = "rgba(30,144,255,0.2)"; },
                        mouseLeave: function(e, link) { link.findObject("HIGHLIGHT").stroke = "transparent"; }
                    },
                    new this.go.Binding("points").makeTwoWay(),
                    this.flow(this.go.Shape, { isPanelMain: true, strokeWidth: 8, stroke: "transparent", name: "HIGHLIGHT" }),
                    this.flow(this.go.Shape, { isPanelMain: true, stroke: "gray", strokeWidth: 2 }),
                    this.flow(this.go.Shape, { toArrow: "standard", stroke: null, fill: "gray" }),
                    this.flow(this.go.Panel, "Auto", { visible: false, name: "LABEL", segmentIndex: 2, segmentFraction: 0.5 },
                        new this.go.Binding("visible", "visible").makeTwoWay(),
                        this.flow(this.go.Shape, "RoundedRectangle", { fill: "#F8F8F8", stroke: null }),
                        this.flow(this.go.TextBlock, "Yes", {
                                textAlign: "center",
                                font: "10pt helvetica, arial, sans-serif",
                                stroke: "#333333",
                                editable: true
                            },
                            new this.go.Binding("text").makeTwoWay())
                    )
                );
        },
        showPorts(node, show) {
            var diagram = node.diagram;
            if (!diagram || diagram.isReadOnly || !diagram.allowLink) return;
            node.ports.each(function(port) {
                port.stroke = (show ? "white" : null);
            });
        },
        save() {
            // document.getElementById("mySavedModel").value = this.myDiagram.model.toJson();
            this.data = this.myDiagram.model.toJson();
            console.log(this.data);
            this.myDiagram.isModified = false;
        },
        load() {
            // this.myDiagram.model = this.go.Model.fromJson(document.getElementById("mySavedModel").value);
            this.myDiagram.model = this.go.Model.fromJson(this.data);
        },
        makeSVG() {
            var svg = this.myDiagram.makeSvg({
                scale: 0.5
            });
            svg.style.border = "1px solid black";
            var obj = document.getElementById("SVGArea");
            obj.appendChild(svg);
            if (obj.children.length > 0) {
                obj.replaceChild(svg, obj.children[0]);
            }
        }
    },
    mounted() {
        let _self = this;
        this.initDiagram();
        // 修改后 自动保存
        this.myDiagram.addDiagramListener("Modified", function(e) {
            _self.save();
        });

        // // 节点单击事件
        //          this.myDiagram.addDiagramListener("ObjectSingleClicked", function(e) {
        //              console.log('单击事件',e.subject.part.data);
        //              alert(e.subject.part.data.key) ;
        //          });

        // 节点双击事件
        this.myDiagram.addDiagramListener("ObjectDoubleClicked", function(ev) {
            alert(ev.subject);
            console.log('双击事件', ev.subject);
            console.log('双击事件', ev.subject.ie);
        });

        // // 节点右击事件
        // this.myDiagram.addDiagramListener("ObjectContextClicked", function (ev) {
        // // ev.preventDefault();
        //     return false;
        // });
        var cxElement = document.getElementById("contextMenu");

        function showContextMenu(obj, diagram, tool) {
            cxElement.style.display = "block";
            var mousePt = diagram.lastInput.viewPoint;
            cxElement.style.left = (mousePt.x + 200) + "px";
            cxElement.style.top = mousePt.y + "px";
        }
        this.myContextMenu = this.flow(this.go.HTMLInfo, {
            show: showContextMenu,
            mainElement: cxElement
        });
        cxElement.addEventListener("contextmenu", function(e) {
            e.preventDefault();
            return false;
        }, false);
        // this.myDiagram.contextMenu = this.myContextMenu;

        // 背景点击事件
        this.myDiagram.addDiagramListener("BackgroundSingleClicked", function(e) {
            console.log(e);
            // alert(e.subject.part.data.key) ;
        });
        //点击放大缩小画布
        this.myDiagram.scale = 0.7;
        this.myDiagram.minScale = 0.4;
        this.myDiagram.maxScale = 1.4;
        $(".enlarge").click(function() {
            _self.myDiagram.scale += 0.1;
        })
        $(".narrow").click(function() {
            _self.myDiagram.scale -= 0.1;
        })
        this.addNodeTplDefault();
        this.addNodeTplStart();
        this.addNodeTplEnd();
        this.addNodeTplComment();
        this.addNodelinkTpl();
        this.initPalette();
        this.load();
    }
};
</script>
<style type="text/css">
/* CSS for the traditional context menu */
#contextMenu {
    z-index: 300;
    position: absolute;
    left: 5px;
    border: 1px solid #444;
    background-color: #F5F5F5;
    display: none;
    box-shadow: 0 0 10px rgba( 0, 0, 0, .4);
    font-size: 12px;
    font-family: sans-serif;
    font-weight: bold;
}
#contextMenu ul {
    list-style: none;
    top: 0;
    left: 0;
    margin: 0;
    padding: 0;
}
#contextMenu li a {
    position: relative;
    min-width: 60px;
    color: #444;
    display: inline-block;
    padding: 6px;
    text-decoration: none;
    cursor: pointer;
}
#contextMenu li:hover {
    background: #CEDFF2;
    color: #EEE;
}
#contextMenu li ul li {
    display: none;
}
#contextMenu li ul li a {
    position: relative;
    min-width: 60px;
    padding: 6px;
    text-decoration: none;
    cursor: pointer;
}
#contextMenu li:hover ul li {
    display: block;
    margin-left: 0px;
    margin-top: 0px;
}
</style>

Tags: