vue + gojs 实现拖拽 流程图 (一)
一、流程图效果
最近一段时间在研究 go.js, 它是一款前端开发画流程图的一个插件,也是一个难点,要说为什么是难点,首先,它是依赖画布 canvas 知识开发。其次,要依赖于内部 API 开发需求,开发项目需求的时候就要花费大量的时间去熟悉 go.js 的 API,然后才能进行开发。话不多说,我就先把我最近做的项目案例效果图展示一下:
看到效果图大家可能会想这个挺简单的,会想没什么难点,其实真正开发的时候才会知道的、才会领悟到。
二、为什么选 go.js 流程图插件去开发项目?
在项目开发一期的时候我用的不是 go.js, 而用的是一款轻便的流程插件 jsplumb.js, 它也集成了各种功能性 API,但是在开发二期的时候它的内部功能已经满足不了需求了,所以我就开始在网上查找流程插件,看了很多插件,比如:G6,D3 等这些可视化流程插件都是不能满足需求。要说为什么不能满足需求, 原因如下:
一、首先,看到效果图里的内置多点和其他模块单点连线问题,其他插件是无法这个满足需求的,可能我没有深入去了解其他的流程插件吧,但是 go.js 里内置点连线可以让开发者很快的理解代码逻辑,不用耗费大量的时间去想点与点的连线。
二、代码上的数据结构问题,其他插件里的 API 数据字段繁琐量多,不够清晰明了,而 go.js 里的数据结构就两个重要字段,一是所有模块的字段集合二是连线字段集合,根据需求可以随意加字段。
三、项目开发
(一)、首先直接使用 go.js,画布中是有水印的
其实这个问题不大,替换一行代码就可以去除水印
引入 go.js 后,直接在编辑器中全局搜索 7eba17a4ca3b1a8346,找到类似这样结构的代码
a.ir=b.W[Ra("7eba17a4ca3b1a8346")][Ra("78a118b7")](b.W,ok,4,4);
注:不同的版本代码不是完全相同的,可能是 a.jv(属性名是会变的) =‘xxxxx’,将这行代码替换成
a.ir=function(){return true;}; //a. 属性名 要保持一致
去除水印的效果
(二)、HTML
1 2 3 4 5 6 7 8 9 | <--第一种--> < template > < div id="wrap"> < div id="chart-wrap"> < div id="chart-palette"></ div ><-- 画布一 --> < div id="chart-diagram"></ div ><-- 画布二 --> </ div > </ div > </ template > |
1 | 如图: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 第二种 结合vue的拖拽组件vuedraggable 实现业务需求。 <template> <div id= "chart-wrap" > <div v- for = "tab in tabLIst" :key= "tab.id" class = "tab" > //拖动 <vuedraggable @end.stop= "end" @start.stop= "move" > <i : class = "tab.icon" /> {{ tab.text }} <el-tooltip effect= "dark" :content= "tab.tooltip" placement= "top" > <i class = "el-icon-question" /> </el-tooltip> </vuedraggable> </div> <div id= "chart-diagram" /> <--画布--> </div> </template> |
1 | 如图: |
(三)、画布的基本设置 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | this .diagram = $(go.Diagram, "chart-diagram" , { // 画布初始位置 initialContentAlignment: go.Spot.LeftSide, // 居中显示 "undoManager.isEnabled" : true , // 支持 Ctrl-Z 和 Ctrl-Y 操作 // 初始坐标 // initialPosition: new go.Point(0, 0), //allowSelect:false, ///禁止选中 // "toolManager.hoverDelay": 100, //tooltip提示显示延时 // "toolManager.toolTipDuration": 10000, //tooltip持续显示时间 // isReadOnly:true,//只读 //禁止水平拖动画布 //禁止水平滚动条 allowHorizontalScroll: false , // 禁止垂直拖动画布 //禁止垂直滚动条 allowVerticalScroll: false , allowZoom: true , //画布是否可以缩放 "grid.visible" : false , //显示网格 // allowMove: true, //允许拖动 // allowDragOut:true, allowDelete: true , //禁止删除节点 allowCopy: true , //禁止复制 // 禁止撤销和重做 // "undoManager.isEnabled": false, // 画布比例 // scale:1.5, // minScale:1.2,//画布最小比例 // maxScale:2.0,//画布最大比例 // 画布初始化动画时间 // "animationManager.duration": 600, // 禁止画布初始化动画 "animationManager.isEnabled" : false , // autoScale:go.Diagram.Uniform,//自适应 // autoScale:go.Diagram.UniformToFill,//自适应 // "draggingTool.dragsLink": false,//拖动线 // autoScale:go.Diagram.None,//默认值不自适应 // 画布边距padding // padding:80或者new go.Margin(2, 0)或new go.Margin(1, 0, 0, 1) // validCycle: go.Diagram.CycleDestinationTree,//只允许有一个父节点 //节点模块动画 S // "animationManager.initialAnimationStyle":go.Animation.EaseOutExpo, // "animationManager.initialAnimationStyle": go.Animation.EaseInOutQuad, "animationManager.initialAnimationStyle" : go.AnimationManager.None, // "animationManager.initialAnimationStyle":go.AnimationManager.AnimateLocations, //节点模块动画 D // validCycle: go.Diagram.CycleNotUndirected, // validCycle: go.Diagram.CycleNotDirected, // validCycle: go.Diagram.CycleSourceTree, //ismodelfied:true //禁止拖拽 // 禁止鼠标拖动区域选中 // "dragSelectingTool.isEnabled" : false, //允许使用delete键删除模块 "commandHandler.deletesTree" : true , // "hasHorizontalScrollbar":false,//去除水平滚动条 // "hasVerticalScrollbar":false,//去除竖直滚动条 // "canStart":false, // allowClipboard: true, // "toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom, //有鼠标滚轮事件放大和缩小,而不是向上和向下滚动 // layout: $(go.TreeLayout, // { angle: 90, layerSpacing: 80 }), } ); |
(三)、整体画布事件及节点的监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // 监听连线 this .diagram.addDiagramListener( "LinkDrawn" , (e) => { console.log(e.subject.part); }); // 监听删除 this .diagram.addDiagramListener( "SelectionDeleted" , (e) => { e.subject.each( function (n) { console.log(n.data.key); }); }) // 修改节点 this .diagram.addDiagramListener( "TextEdited" , (evt) => { console.log(e.subject.part); }); // 监听点击 this .diagram.addDiagramListener( "ObjectSingleClicked" , (e) => {<br> <br> //这是清除高亮的 // e.diagram.commit((d) => { // d.clearHighlighteds(); // }, "no highlighteds"); }); // // 移动事件 this .diagram.addDiagramListener( "SelectionMoved" , (e) => { console.log(e.diagram.lastInput.documentPoint); }); |
(四)、连线点封装函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | // 节点连接线 makePort (name, align, spot, output, input) { var horizontal = align.equals(go.Spot.Top) || align.equals(go.Spot.Bottom) return $(go.Shape, { fill: 'transparent' , // 默认透明不现实 strokeWidth: 0, // 无边框 fromMaxLinks: 1, // width: horizontal ? NaN : 10, // 垂直"port"则8像素宽 height: !horizontal ? NaN : 5, // 水平"port"则8像素 alignment: align, // 同其模块对齐 stretch: horizontal ? go.GraphObject.Horizontal : go.GraphObject.Vertical, // 自动同其模块一同伸缩 portId: name, // 声明ID fromSpot: spot, // 声明连线头连出此"port"的位置 fromLinkable: output, // 布尔型,是否允许连线从此"port"连出 toLinkable: input, // 布尔型,是否允许连线从此"port"连出 toSpot: spot, // 声明连线尾连入此"port"的位置 cursor: 'pointer' , // 鼠标由指针改为手指,表示此处可点击生成连线 mouseEnter: function (e, port) { // 鼠标移到"port"位置后,高亮 if (!e.diagram.isReadOnly) port.fill = 'rgba(255,0,255,0.3)' }, mouseLeave: function (e, port) { // 鼠标移出"port"位置后,透明 port.fill = 'transparent' } }) } |
(五)、节点连线的高亮函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | isHighlightedFun(link) { return $$(go.Shape, "RoundedRectangle" , { fill: "rgba(217,236,255,.2)" , stroke: "rgba(39,154,242,.1)" , strokeWidth: 1, }, new go.Binding( "stroke" , "isHighlighted" , (h) => { return h ? "rgba(39,154,242,1)" : "rgba(39,154,242,.8)" ; }).ofObject(), new go.Binding( "strokeWidth" , "isHighlighted" , (h) => { return h ? 2.5 : 1.3; }).ofObject(), ) } |
如图:
(六)、 节点连接线。(方法封装)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | makePort(name, align, spot, output, input) { var horizontal = align.equals(go.Spot.Top) || align.equals(go.Spot.Bottom); return $$(go.Shape, { fill: "transparent" , // 默认透明 strokeWidth: 0, // 无边框 fromMaxLinks: 1, //最大连接数 width: horizontal ? NaN : 10, // 垂直"port"则8像素宽 height: !horizontal ? NaN : 2, // 水平"port"则8像素 alignment: align, // 同其模块对齐 stretch: (horizontal ? go.GraphObject.Horizontal : go.GraphObject.Vertical), //自动同其模块一同伸缩 portId: name, // 声明ID fromSpot: spot, // 声明连线头连出此"port"的位置 fromLinkable: output, // 布尔型,是否允许连线从此"port"连出 toLinkable: input, // 布尔型,是否允许连线从此"port"连出 toSpot: spot, // 声明连线尾连入此"port"的位置 cursor: "pointer" , // 鼠标由指针改为手指,表示此处可点击生成连线 mouseEnter: (e, port) => { //鼠标移到"port"位置后,高亮 if (!e.diagram.isReadOnly) port.fill = "rgba(255,0,255,0.3)" ; }, mouseLeave: (e, port) => { // 鼠标移出"port"位置后,透明 port.fill = "transparent" ; } }); }注意:要在 this .diagram.nodeTemplateMap.add 增加模块里调用。 示例: this .diagram.nodeTemplateMap.add(type, $$(go.Node, "Auto" , this .makePort( "T" , go.Spot.Top, go.Spot.TopCenter, false , true ) )) |
四、当前画布的监听事件名称包括
- "AnimationStarting" :动画渲染前事件, 加载图表的动画即将开始;
- "AnimationFinished" :动画渲染完事件, 加载图表刚刚完成的动画;
- "BackgroundSingleClicked": 背景单击事件, 单击图表背景;
- "BackgroundDoubleClicked" :背景双击事件, 双击图表背景;
- "BackgroundContextClicked" :背景右键事件, 右键单击图表背景;
- "ChangingSelection": 改变选择前事件, 一个操作即将改变 Diagram.selection 图表选择集合,
- "ChangedSelection" :改变选择后事件, 一个操作已经改变?Diagram.selection 图表选择集合,
- "ClipboardChanged" :剪切板改变事件, 零部件已被 CommandHandler.copySelection 复制到剪贴板上;
- "ClipboardPasted" :剪切板粘贴事件, 零部件已由 CommandHandler.pasteSelection 从剪贴板复制到图表中;
- "DocumentBoundsChanged": 文档范围改变事件, 图表中各零部件的面积,?Diagram.documentBounds, 已经改变了;
- "ExternalObjectsDropped" :(节点或线等) 零部件拖放生成事件, 零部件已经通过拖拽从图的外部复制到图中;
- "GainedFocus" :获得键盘焦点事件, 该图获得了键盘焦点,例如在调用 Diagram.focus 之后.
- "LayoutCompleted" :布局完成事件, 整个图表布局刚刚更新;
- "LinkDrawn": 线创建事件, 用户刚刚使用 LinkingTool 创建了一个新链接;?
- "LinkRelinked": 线重新连接事件, 用户刚刚通过 RelinkingTool? 或?DraggingTool 重新连接了现有线;
- "LinkReshaped": 线路径改变事件, 用户刚刚通过 LinkReshapingTool 调整了线的路径;
- "LostFocus" :图表失去焦点事件, 这个图表失去了键盘焦点,
- "Modified" :图表改变事件,?Diagram.isModified? 属性已被设置为一个新值——用于将窗口标记为自上次保存以来已被修改;
- "ObjectSingleClicked": 对象单击事件, 单击了图形对象(节点和线等);
- "ObjectDoubleClicked":双击了图形对象(节点和线等);
- "ObjectContextClicked":右键单击了图形对象(节点和线等);
- "PartCreated" :Part 创建事件, 用户通过?ClickCreatingTool 插入新的零部件;
- "PartResized" :Part 大小改变事件, 用户通过 ResizingTool 调整工具改变了一个图形对象的大小;
- "PartRotated" :Part 旋转事件, 用户通过 RotatingTool 旋转工具改变了一个图形对象的角度 ;
- "SelectionMoved" :拖动事件, 用户通过 DraggingTool 拖动工具移动了选定的部分;
- "SelectionCopied" :复制事件, 户通过 DraggingTool 拖动工具复制了选定的部分;
- "SelectionDeleted" :删除后事件, 用户通过?CommandHandler.deleteSelection? 已经删除了选定的部分;
- "SelectionDeleting" :删除前事件, 用户通过?CommandHandler.deleteSelection 即将删除选定的部分;
- "SelectionGrouped" :选择创建分组事件, 通过 CommandHandler.groupSelection 已经从选择的零部件中创建了一个新的组 ;
- "SelectionUngrouped":用户已删除选定的组,但通过 CommandHandler.ungroupSelection 保留其成员;
- "SubGraphCollapsed" :子图折叠事件, 用户通过 CommandHandler.collapseSubGraph 将选定的组折叠;
- "SubGraphExpanded" :子图展开事件, 用户通过 CommandHandler.expandSubGraph 将选定的组展开;
- "TextEdited" :文本块修改事件, 用户通过文本编辑工具改变了文本块的字符串值;
- "TreeCollapsed": 树折叠事件, 用户通过 CommandHandler.collapseTree 折叠所选节点的子树;
- "TreeExpanded" :树展开事件, 用户通过 CommandHandler.expandTree 展开了所选节点的子树;
- "ViewportBoundsChanged":视窗范围改变事件, 图表中可见的区域,?Diagram.viewportBounds, 发生了改变;
如有需求或者疑问加 QQ 讨论。 先写到这,下周写整体迭代更新(二)看下一篇, 如有不足 可以私信单聊共享技术经验。。。。。。。。。。。。。。