当前位置: 首页 > Web前端 > HTML

基于Vue的简单流程图开发

时间:2023-04-02 21:56:17 HTML

严重拖沓。一方面,本项目模块纯属个人娱乐。另一方面,流程图涉及的东西还是挺多的,这次只介绍一些简单的部分。拖了这么久,现在终于要硬着头皮写一篇基于vue+svg流程图的“伪教程”了。第一次露丑,请轻喷。模块介绍项目地址是为了学习Vue而不是为了兼容。这个项目只考虑现代浏览器(谷歌)。请原谅我的一些兼容性问题。本模块的开发源于对流程图的简单需求(纯UI实现,暂不存在业务逻辑),这里不再赘述vue-cli生成的目录结构(可以参考这篇文章)或谷歌自己)。项目中实际使用的技术栈:SVG+vue+vuex功能介绍:Canvas缩放节点(start,base,judgement等)添加,删除节点间的连接(直线/折线)Text添加externalimportSVG图形Undoand重做canvasScaling考虑到缩放后canvas的布局需要保持一致,这里通过修改transform:scale();来实现;变换原点:;并且节点相对于父层定位。TODO:SVG最佳缩放解决方案?节点相关先简单说一下思路:既然没有业务逻辑,我简化流程图开始基本判断三个基本组件(基于SVG)。如:这里说一下判断这个组件(后期可能出现的复杂形状都是通过path实现的),一般直接用AI软件形状导出。左侧工具栏和画布中相同的图形来自同一个组件,所以有两种样式,分别是defaultStyle和drawStyle。我之前考虑过,如果流程图的图形复杂多变,那这种模式的每一个组成部分岂不是都要手动定义。同样,导入SVG也存在类似问题。因为如果图形的大小不确定,除了支持修改图形的大小外,否则会导致画布上出现不同大小的图形。(遗憾的是这方面一直没有突破,但这会成为以后改进的方向。)初步的解决方案是使用缩放的方法,即统一制作工具栏中的图形和拖动的图形进入画布形成等比例缩放关系。但是这种方法会导致笔画也同比缩放,这不是我们想要的。所以暂时采用写死的方式。注意:在svg中,椭圆是相对于中心点定位的,而矩形是相对于左上角定位的。TODO有没有办法将每个组件的定位源点设置为组件的中心点。节点渲染在节点渲染方面,由于之前都是将图形作为组件,所以采用组件+的方式来渲染图形。同时也是以数据驱动的方式进行渲染,即数据决定视图。当拖动节点涉及镜像节点时:代码通过train添加节点拖拽表单。使用这种方法的好处是不需要模拟拖动事件。也就是说,你不需要自己做镜像什么的。(原生模拟用于在画布中拖放节点。)代码直通车对节点的操作是指令(directives)的形式(直接操作DOM)。这让我质疑Vue框架是否适合这类项目。从开发效率的角度,Vue还是首选,但是从性能的角度,由于没有深入研究,我没有发言权。TODO场景模拟,假设我们需要移动画布中的节点,通过指令的el获取节点,然后通过el.onmousemove修改数据中对应的translate,实现位置变化。这里修改数据来驱动视图是我们常用的方式,但是我想不通的是el.onmousemove实现的双向数据绑定修改数据所带来的性能是否体现在这里。我想象的是,diff带来的性能提升只有在涉及多个依赖时才有价值。比如我有一个列表,data中存在listData,然后view中有多个关联的listData。这时候操作listData比直接操作DOM要好。看了vitrualDOM的介绍,只能通过diff操作变化的DOM。获取SVG大小,使用getBoundingClientRect获取节点大小,由于前面做了缩放函数,所以这里获取节点大小的时候,需要除以缩放比例才能得到正确的值。让obj=el.getElementsByTagName('g')[0]让w=obj.getBoundingClientRect().width/_this.drawStyle.zoomRateleth=obj.getBoundingClientRect().height/_this.drawStyle.zoomRateletwh={width:w,height:h}代码通过train节点操作总结由于节点的显示是基于NodeData的,增删其实就是增删NodeData。主要代码connection相关的connection其实只用到了svg的line和polyline,类似于节点,以组件的形式存在,connectionview由lineData驱动。所以最终连接的增删改查也是对数据的操作。连接点的显示首先是连接点的位置(绿色远点位置),之前的流程图基于jquery的是使用div布局,现在使用svg增加了难度,由于svg不能使用position,所以不能根据当前元素定位,采用土方法,即通过使用动态获取4个点的位置graphicsize+padding期间,由于连续4条线节点和graph节点之间存在间隙,当mouseover不在graph或node中时,无法触发事件,这个通过模拟一个区域来解决.由于个人经验,这部分代码完全是命令式风格,请勿直通喷代码汽车代码throughtrain2节点间连接处理有两种情况:(具体我就不说了从mousedown到mouseup的细节在这里,可以看这里)其实很多人说这个算法可以解决很多垃圾代码。遗憾的是,我还没有掌握它的真正含义,比如前面的图形组件和后面的不同线条,其实是可以通过一定的算法得到的。我这里只说最笨的方法。等我长大了可以用算法说话了,在回来照顾这篇文章。line一条直线无非是两个点坐标,在svg中通过line显示。这个时候就要看项目的要求了。我们假设最简单的情况就是上面提到的四个连接点是连接最多的起点或终点。下面是计算4个点在图中的坐标位置computeLine(direction,obj){//low不止一点点let{top,left,width,height}=objletw=width/2leth=height/2开关(方向){case't':top=top-hbreakcase'b':top=top+hbreakcase'l':left=left-wbreakcase'r':left=left+wbreakdefault:break}return{top,left}}PolylinePolylinePolyline考虑的情况多一点。由于这里使用的是折线,所以它的点设置是这样的:points="125,96183.5,96183.5,399242,399"一般都是把字符转成数组或者对象,比较容易操作。折线中涉及的起点和终点与上面介绍的直线的点相同。区别在于中线的位置。如果不考虑复杂的情况,一般可以分为上下左右两种。通过获取起点和终点的中点位置来确定中线,就可以得到想要的折线。代码如下:(都是简单粗暴的方式。)computePolyLine(start,end,direction){letstartPoint={x:+(start.split(',')[0]),y:+(start.split(',')[1])}letendPoint={x:+(end.split(',')[0]),y:+(end.split(',')[1])}让m1,m2切换(方向){case't':case'b':letmY=startPoint.y+(endPoint.y-startPoint.y)/2m1={x:startPoint.x,y:mY}m2={x:endPoint.x,y:mY}中断case'l':case'r':让mX=startPoint.x+(endPoint.x-startPoint.x)/2m1={x:mX,y:startPoint.y}m2={x:mX,y:endPoint.y}中断默认值:中断}返回`${startPoint.x},${startPoint.y}${m1.x},${m1.y}${m2.x},${m2.y}${endPoint.x},${endPoint.y}`}ConnectionSummary节点和连接在渲染和操作处理上是相似的。有两个地方不确定是不是最佳实践。一是使用component+是渲染组件,二是使用指令操作DOM。连接的计算形式也有些简单,确实需要一定的时间才能成长起来。有点偏,这里简单总结一下,不管是哪种连接方式,我们要做的就是正确获取对应点的位置,然后修改数据驱动视图。不过能在各种复杂的情况下总结算法也是一个飞跃,加油。给节点和行添加文本的原理是一样的。这里我们使用设置contenteditable。当contenteditable为true时,html结构自动添加文本节点,可以编辑。更多细节请参考张新旭的文章。顺便说一下,我们来谈谈指针事件。这个模块在两个地方使用了这个css属性。一个是添加这段文字,还有headertoolbar部分。CSS属性pointer-events允许作者控制特定图形元素何时成为鼠标事件的目标。如果未指定此属性,则SVG内容的行为与visiblePainted相同。除了指定该元素不是鼠标事件的目标之外,none值指示鼠标事件通过该元素并到达位于其下方的元素。关于pointer-events的更多细节ZhangXinxuMDNTODO虽然已经实现了文本编辑功能,但是这部分BUG很多,还没有完善。SVG的外部导入也使用了HTML5的drop功能,使用svg的图片进行展示。拖拽实现比较简单:dropHandle(e){letreader=newFileReader()letfile=e.dataTransfer.files[0]reader.onload=(e)=>{this.userImages.push(e.target.result)}reader.readAsDataURL(file)},dragoverHandle(){},dragstart(imgSrc){event.dataTransfer.setData('URL',imgSrc)}这里需要注意的是@drop.stop.prevent="dropHandle"@dragover.stop.prevent="dragoverHandle"以防止冒泡和浏览器默认行为。另外需要注意的是,dataTransfer.getData()无法在dragover、dragenter、dragleave中获取数据。根据W3C标准,拖动数据存储有三种模式,Read/writemode,Read-onlymode和Protectedmode。DetailsRead/writemoderead/write模式,用在dragstart事件中,可以向拖拽数据存储添加新的数据。只读模式,在drop事件中使用,可以读取拖拽的数据,但是不能添加新的数据。保护模式,用于所有其他事件,可以枚举数据列表,但数据本身不可用,不能添加新数据。深度undo和redo的功能本质上是不完整的,因为采用了惰性的方式,vuex生成State快照,生产环境不推荐使用。基本原理是通过vuexsubmithigher(mutation)触发回调。这个是用来记录通过train的状态快照的代码总结这个项目属于vue+vuex的入门,但是不讲怎么用vue或者vuex,因为这些在官方文档里面其实已经很清楚了。项目也简单的使用了vue的自定义指令和MiXin等常用方法。vueRender等功能组件不在本文讨论范围之内。这里我们简单说说用户体验。render组件更适合高度定制化的组件(变化逻辑更复杂)。因为一些简单的组件其实更适合tempalte。虽然使用Render可以提升一定的性能(减少tempalte到render的步骤),但是很多现有的组件比如sync在render组件中是没有的(需要自己实现。)。在使用vuex的时候需要注意对象引用地址的问题。即避免数据之间的潜在影响。(虽然vuex本身也避免了这个问题)可以了解下immutable。本教程主要讲如何基于Vue实现一个简单的流程图,更多思考的是,哪些项目比较适合使用这个MVVM模型框架,如何发挥VitrualDOM的价值。其实只要把上面几章的要点拿出来,就可以深入探讨很多技术问题,以后有机会再深入。