当前位置: 首页 > 科技观察

从去年推出的可视化编辑器H5-dooring,从零开始开发可视化构建框架Dooringx-Lib

时间:2023-03-14 19:22:38 科技观察

已经一年了。这期间,很多热心的网友和大佬们提出了很多宝贵的建议。我们也在逐步实现中,以下是一些典型的低代码可视化平台需求:代码输出能力(即源码下载功能)组件交互(即组件支持业务中常用的链接跳转、弹窗交互、自定义事件等)数据源管理(即用户创建的不同页面具有共享数据的能力,不同的组件也具有共享数据的能力)组件存储(即用户可以独立生产组件、定义组件、访问组件数据)布局能力(即用户可以选择不同的布局方案来设计页面)常用功能集成(页面截图、微信分享、调试能力)以上功能需求在中已经实现H5-dooring,在我之前的文章中有相应的技术分享。但是为了让更多的人低成本的拥有自己的可视化构建系统,我们团队的大佬们花了很多时间去研究和沉淀,最近还开源了一个可视化构建框架dooringx-lib,我们可以使用在不考虑内部实现细节的情况下,很容易做出可视化编辑器。接下来就给大家分享一下这个可视化框架的使用和实现思路。同时也非常感谢dooring可视化团队的辛勤付出。可视化构建框架的基本使用和技术实现为了让大家更好的理解可视化构建框架,这里举几个形象的例子:antd——antd-pro我们都知道antd是一个流行的前端组件库,所以基于它的上层封装管理后台antd-pro就是它的上层应用。GrapesJS——craft.jsGrapesJS是国外的一个页面编辑器框架(具体可以参考我之前的文章,这个国外的开源框架可以让你轻松搭建自己的页面编辑器),那么craft.js就是它的上层应用框架。dooringx-lib——dooringxdooringx-lib是一个可视化构建框架,类似dooringx是一个基于dooringx-lib的可视化编辑器。之所以要介绍它们的区别,是因为很多朋友之前对这个概念理解不是很清楚。了解了可视化框架的“内涵”之后,我们就开始今天的核心内容。1、在分享框架实现思路之前,技术栈当然要先汇报一下自己。框架实现我们还是使用熟悉的React生态。移动端组件库采用众安团队的Zarm,编辑器应用层采用antd。至于其他,比如拖拽、参考线、状态管理、插件机制等,都是我们组长自研的解决方案。如果你是vue或者其他基于技术栈的团队,也可以参考一下实现思路,相信也会对你有所启发。2.基本使用在开始深入之前,让我们看看如何使用这个框架。我们只需要通过以下方式安装使用即可:npm/yarninstalldooringx-lib。同时,我们也提供了一个基础的demo,方便大家使用。项目中快速上手:#clone项目#cnpmjsgitclonehttps://github.com.cnpmjs.org/H5-Dooring/dooringx.git#orgitclonehttps://github.com/H5-Dooring/dooringx.git#进入项目目录cddooringx#安装依赖yarninstall#启动基础示例yarnstart:example#启动dooringx-libyarnstart#启动dooringxdoc文档yarnstart:doyarnbuilddemogithub项目如下:github地址:https://github.com/H5-Dooring/dooringx后了解了使用方法,我们来看看基本架构和实现思路。三、dooringx-lib的基本结构和工作机制上图是我根据dooringx-lib目前的项目结构整理出来的结构图,基本包含了构建编辑框架所需要的大部分模块。为了保证框架的灵活性,我们还可以根据需要安装相应的功能组件,开发自定义组件等。下面是一个基本的导入案例:import{RightConfig,Container,useStoreState,innerContainerDragUp,LeftConfig,ContainerWrapper,Control,}来自'dooringx-lib';我们将整个框架拆分成不同的模块,这些模块相互独立,又可以相互关联。完整的工作流程如下:从上图可以看出,我们只需要具备基本的业务研发能力,就可以借助dooringx-lib搭建自己的平台,就像任何程序的本质:数据和逻辑。4.dooringx-lib插件开发接下来给大家分享一下dooringx-lib的插件开发方法和具体实现(如何导入插件,如何编写组件,如何注册函数等),如果你有兴趣,也可以按照下面的方式实践一下。4.1如何导入组件我们在上图中可以看到,左边是我们的组件素材区,分为基础组件、媒体组件和视觉组件。它们的添加将在LeftRegistMap数组中进行管理。基本结构如下:constLeftRegistMap:LeftRegistComponentMapItem[]=[{type:'basic',//组件类别component:'button',//组件名称img:'icon-anniu',//组件icondisplayName:'button',//组件中文名urlFn:()=>import('./registComponents/button'),//注册回调},];左侧组件支持同步导入或异步导入。如果需要异步导入组件,需要填写urlFn,还需要一个返回promise的函数。还可以支持远程加载组件,只要装上webpack即可。如果需要同步导入组件,需要将组件放在配置项的initComponentCache中,这样加载时会注册到componentRegister中。initComponentCache:{modalMask:{component:MmodalMask},},4.2如何自定义左面板左面板可以传入leftRenderListCategory。leftRenderListCategory:[{type:'basic',icon:,displayName:'BasicComponent',},{type:'xxc',icon:,custom:true,customRender:

I是自定义渲染
,},],type是category,左边的组件显示在哪个category中,由这个字段决定。icon是左边分类的小图标(如上图)。当custom为true时,您可以使用customRender自定义渲染。4.3开发自定义可视化组件组件需要导出一个ComponentItemFactory生成的对象:constMButton=newComponentItemFactory('button','button',{style:[createPannelOptions('input',{receive:'text',label:'text',}),],animate:[createPannelOptions('animateControl',{})],actions:[createPannelOptions('actionButton',{})],},{props:{...text:'x.dooring'//输入配置项组件收到的初始值},},(data,context,store,config)=>{return;},true);exportdefaultMButton;其中第一个参数为组件注册名,第二个参数为used表示使用。第三个参数用于配置右侧面板的配置项组件。key为右侧面板的类别,value为配置项组件数组。第四个参数将配置组件的初始值。需要注意的是,生产组件必须有初始宽高(不是被内容扩展),否则适配时全选会出问题。这个初始值有很多有用的属性。例如,fixed表示使用固定定位。该值可以结合配置项进行修改,从而修复组件。还有类似于lock命令的canDrag,锁定的元素不能拖动。初始值中的rotate需要一个对象,value表示旋转角度,canRotate表示是否可以操作旋转。(0.7.0版本支持)第五个参数是一个函数,会从配置项中的receive属性中获取配置(暂且默认配置为receive)。比如上面的例子,接收的是text,那么函数Thisfield就会接收到data中。上下文一般只有预览和编辑,用于环境判断。config可以获取所有的数据,并在制作事件时使用它。第六个参数resize是判断是否可以进行缩放。为假时,不能进行缩放。第七个参数needPosition,部分组件移入画布后默认为拖动点。这个配置项默认为true,也就是需要拖动的位置。当为false时,将使用组件本身的top和left定位来放置它。4.4事件注册注册计时事件可以细分为注册计时和函数,注册计时可以通过组件中的hook来实现:useDynamicAddEventCenter(pr,`${pr.data.id}-init`,'初始渲染计时');//注册名必须有id约定!useDynamicAddEventCenter(pr,`${pr.data.id}-click`,'点击执行时机');useDynamicAddEventCenter的第一个参数是由render的四个参数组成的一个对象。第二个参数是注册时的名字,必须和id相关。这个是约定俗成的,不然多个组件可能会引起名字冲突,找的时候方便。计时注册完成后,我们需要将计时放到相应的触发位置。比如这个按钮的点击执行时机放在onclick中:{eventCenter.runEventQueue(`${pr.data.id}-click`,pr.config);}}>x.dooring其中第一个参数是注册时间名称,第二个是渲染函数中的最后一个参数。config函数注册函数由组件抛出,可以加载到事件链上。比如我注册了一个改变文本的函数,我可以在任何一个组件的时机调用这个函数,从而触发组件改变文本。函数注册需要放入useEffect,卸载组件时需要卸载函数!否则,会导致函数越来越多。useEffect(()=>{constfunctionCenter=eventCenter.getFunctionCenter();constunregist=functionCenter.register(`${pr.data.id}+更改文本函数`,async(ctx,next,config,args,_eventList,iname)=>{constuserSelect=iname.data;constctxVal=changeUserValue(userSelect['更改文本数据源'],args,'_changeval',config,ctx);consttext=ctxVal[0];setText(text);next();},[{name:'更改文本数据源',data:['ctx','input','dataSource'],options:{receive:'_changeval',multi:false,},},]);return()=>{取消注册();};},[]);函数中的参数和配置参考下面的函数开发。4.5右侧面板开发为了开发一个自定义的右侧属性面板,我们只需要将开发好的组件对象放入initFormComponents中即可。为了获得良好的开发体验,您需要定义一个formMap类型:键名是initFormComponents的键名,formMap的值对应组件需要接收的值。以input组件为例,FormInputType现在有2个属性:label、receive。那么在开发这个组件的时候,props会收到:interfaceMInputProps{data:CreateOptionsRes;current:IBlockType;config:UserConfig;}即data是formMap类型,current是当前点击的组件,config不用说。还记得左边组件开发中的第三个参数吗?这都是相关的:style:[createPannelOptions('input',{receive:'text',label:'text'})],createPannelOptions这个函数的泛型填写对应的组件,这将为收到的配置项提供很好的提示。在配置项组件中只需要从组件中接收配置项,然后修改当前属性即可:|{};},[props.data]);return({(optionsany)?.label||'text'}:{constreceive=(optionsany).receive;constclonedata=deepCopy(store.getData());constnewblock=clonedata.block.map((v:IBlockType)=>{if(v.id===props.current.id){v.props[receive]=e.target.value;}returnv;});store.setData({...clonedata,block:[...newblock]});}}>);}由于store很容易获取,因此可以在任何地方修改数据。将组件的值与当前属性相关联,并使用onChange修改store,从而完成双向绑定。注意:如果你的右侧组件需要使用block以外的属性,你可能需要判断它是否是pop-up模式。4.6自定义右键菜单可以自定义右键菜单://自定义右键constcontextMenuState=config.getContextMenuState();constunmountContextMenu=contextMenuState.unmountContextMenu;constcommander=config.getCommanderRegister();constContextMenu=()=>{constandleclick=()=>{unmountContextMenu();};constforceUpdate=useState(0)[1];contextMenuState.forceUpdate=()=>{forceUpdate((pre)=>pre+1);};return({commander.exec('redo');handleclick();}}>
);};contextMenuState.contextMenu=;首先获取contextMenuState,contextMenuState上有个unmountContextMenu关闭右键菜单。所以点击后需要调用close。同时上面的left和top是右键的位置。另外,我们还需要在组件中添加一个强刷,并赋值给forceUpdate,让组件移动时跟随。4.7表单验证提交思路表单验证提交的方式有很多种,因为所有的数据都是打通的,也可以直接写一个表单组件。当不使用表单组件时,简单的方法是为每个输入组件制作一个验证功能和提交功能。这样,是否验证取决于用户的选择,抛出的输入允许用户选择放在哪里,用户可以给变量命名。当点击提交按钮时,会调用所有组件的校验函数和提交函数,将它们抛到上下文中,然后通过上下文聚合函数聚合成对象,最后通过发送函数发送到对应的后端,这样就完成了整个过程。我们可以在dooringx中试试这个demo。另一种方式是专门写一个提交按钮,参数固定,有一些规则,比如规定页面上的所有表单都会被收集并提交。那么我们就可以使用数据源自动将所有的表单输出内容提交给数据源,最后的提交按钮根据数据源指定的格式提取key发送给后台。在规划的后期,我们会不断迭代优化产品功能。如果您有好的建议,也可以随时与我们沟通。也欢迎大家在github上积极提issue。