前端:从零开发一款可视化搭建框架
ccwgpt 2024-09-13 16:15 32 浏览 0 评论
去年上线的可视化编辑器 H5-dooring 至今已有一年的时间,期间有很多热心的网友和大佬提出了非常多宝贵的建议,我们也在一步步实现中,以下是几个比较典型的低代码可视化平台需求:
- 出码能力(即源码下载功能)
- 组件交互(即组件支持业务中常用的链接跳转,弹窗交互,自定义事件等)
- 数据源管理(即用户创建的不同页面拥有共享数据的能力,不同组件之间也有共享数据的能力)
- 组件商店(即用户可以自主生产组件,定义组件,接入组件数据的能力)
- 布局能力(即用户可以选择不同的布局方案来设计页面)
- 常用功能集成(页面截图,微信分享,debug能力)
上面的这些功需求已经在 H5-dooring 陆续实现了,在我之前的文章中也有对应的技术分享。但是为了让更多的人能低成本的拥有自己的可视化搭建系统,我们团队的大佬花了非常多的时间研究和沉淀,最近也开源了一款可视化搭建框架 dooringx-lib,我们可以基于它轻松制作可视化编辑器,而无需考虑内部的实现细节,接下来我就和大家分享一下这款可视化框架的使用方式和实现思路,同时也非常感谢 dooring可视化团队 各位大佬们的辛勤付出。
可视化搭建框架基本使用和技术实现
为了让大家更好的理解可视化搭建框架,我这里举几个形象的例子:
- antd —— antd-pro
我们都知道 antd 是流行的前端组件库,那么基于它上层封装的管理后台 antd-pro 就是它的上层应用。
- GrapesJS —— craft.js
GrapesJS 是一款国外的页面编辑器框架(详细介绍可参考我之前的文章 这款国外开源框架, 让你轻松构建自己的页面编辑器) ,那么 craft.js 就是它的上层应用框架。
- dooringx-lib —— dooringx
dooringx-lib 是一款可视化搭建框架,同理 dooringx 就是基于 dooringx-lib 的可视化编辑器。
之所以要介绍它们的区别,是因为之前有很多朋友对这块概念理解的不是很清晰,在了解了可视化搭建框架 的 “内涵” 之后,我们开始今天的核心内容。
1.技术栈
在分享框架实现思路之前当然要自报家门,框架实现上我们还是采用熟悉的 React 生态,移动端组件库采用的众安团队的 zarm,编辑器应用层采用的 antd,至于其他的比如拖拽,参考线,状态管理,插件机制等都是我们团队大佬自研的方案。如果你是 vue 或者其他技术栈为主的团队,也可以参考实现思路,相信也会对你有一定的启发。
2.基本使用方式
在开始深入之前我们先看看如何使用这款框架,我们只需要按照如下方式即可安装使用:
npm/yarn install dooringx-lib
同时我们还提供了基础的使用demo,方便大家在自己的工程中快速上手:
# 克隆项目
# cnpmjs
git clone https://github.com.cnpmjs.org/H5-Dooring/dooringx.git
# or
git clone https://github.com/H5-Dooring/dooringx.git
# 进入项目目录
cd dooringx
# 安装依赖
yarn install
# 启动基础示例
yarn start:example
# 启动 dooringx-lib
yarn start
# 启动 dooringx doc 文档
yarn start:doc
yarn build
demo 的 github 项目如下:
github地址: https://github.com/H5-Dooring/dooringx
在了解完使用方式之后,我们来看看基本架构和实现思路。
3.dooringx-lib基础架构和工作机制
上图就是我根据目前 dooringx-lib 的项目架构梳理的架构图,基本包含了搭建化编辑框架的大部分必备模块。为了保证框架的灵活性,我们还可以按需安装对应的功能组件,开发自定义的组件等。如下是一个基本的导入案例:
import {
RightConfig,
Container,
useStoreState,
innerContainerDragUp,
LeftConfig,
ContainerWrapper,
Control,
} from 'dooringx-lib';
我们将整个框架拆分成了不同的模块,这些模块既相互独立又可以相互关联。完整的工作流程如下:
由上图可以看出,我们只需要拥有基础的业务研发能力,就可以借助 dooringx-lib 构建一个属于自己的搭建平台,就好比任何程序的本质: 数据和逻辑。
4.dooringx-lib插件开发
接下来我会和大家分享 dooringx-lib 的插件开发方式和具体实现(如何导入插件,如何编写组件,如何注册函数等),如果大家感兴趣的话也可以跟着下面的方式实践一下。
4.1 如何导入组件
我们在上图可以看到左侧是我们的组件物料区,分为基础组件,媒体组件,可视化组件,它们的添加会统一放在 LeftRegistMap 数组中来管理,其基本结构如下:
const LeftRegistMap: LeftRegistComponentMapItem[] = [
{
type: 'basic', // 组件类别
component: 'button', // 组件名称
img: 'icon-anniu', // 组件icon
displayName: '按钮', // 组件中文名
urlFn: () => import('./registComponents/button'), // 注册回调
},
];
左侧组件支持同步导入或者异步导入。
如果需要异步导入组件,则需要填写 urlFn,需要一个返回 promise 的函数。也可以支持远程载入组件,只要 webpack 配上即可。
如果需要同步导入组件,则需要将组件放入配置项的 initComponentCache 中,这样在载入时便会注册进 componentRegister 里。
initComponentCache: {
modalMask: { component: MmodalMask },
},
4.2 如何定制左侧面板
左侧面板传入 leftRenderListCategory 即可。
leftRenderListCategory: [
{
type: 'basic',
icon: <HighlightOutlined />,
displayName: '基础组件',
},
{
type: 'xxc',
icon: <ContainerOutlined />,
custom: true,
customRender: <div>我是自定义渲染</div>,
},
],
type 是分类,左侧组件显示在哪个分类由该字段决定。icon 则是左侧分类小图标(如上图所示)。当 custom 为 true 时,可以使用 customRender 自定义渲染。
4.3 开发一个自定义的可视化组件
组件需要导出一个由 ComponentItemFactory 生成的对象:
const MButton = new ComponentItemFactory(
'button',
'按钮',
{
style: [
createPannelOptions<FormMap, 'input'>('input', {
receive: 'text',
label: '文字',
}),
],
animate: [createPannelOptions<FormMap, 'animateControl'>('animateControl', {})],
actions: [createPannelOptions<FormMap, 'actionButton'>('actionButton', {})],
},
{
props: {
...
text:'x.dooring'// input配置项组件接收的初始值
},
},
(data, context, store, config) => {
return <ButtonTemp data={data} store={store} context={context} config={config}></ButtonTemp>;
},
true
);
export default MButton;
其中第一个参数为组件注册名,第二个参数用来展示使用。
第三个参数用来配置右侧面板的配置项组件。其中键为右侧面板的分类,值为配置项组件数组。
第四个参数会配置组件的初始值,特别注意的是,制作组件必须要有初始宽度高度(非由内容撑开),否则会在适配时全选时产生问题。
这个初始值里有很多有用的属性,比如fixed代表使用固定定位,可以结合配置项更改该值,使得组件可以fixed定位。
还有 canDrag 类似于锁定命令,锁定的元素不可拖拽。
初始值里的 rotate 需要个对象,value 代表旋转角度,canRotate 代表是否可以操作旋转。(0.7.0版本开始支持)
第五个参数是个函数,你将获得配置项中的 receive 属性(暂且都默认该配置为receive)传来的配置,比如上例中 receive 的是 text,则该函数中 data 里会收到该字段。
context 一般只有 preview 和 edit,用来进行环境判断。
config 可以拿到所有数据,用来制作事件时使用。
第六个参数 resize 是为了判断是否能进行缩放,当为 false 时,无法进行缩放。
第七个参数 needPosition,某些组件移入画布后会默认采取拖拽的落点,该配置项默认为 true, 就是需要拖拽的位置,为 false 时将使用组件自身 top 和 left 定位来放置。
4.4 事件注册
- 注册时机
事件可以细分为 注册时机 和 函数,组件内可以通过 hook 的方式来实现注册时机:
useDynamicAddEventCenter(pr, `${pr.data.id}-init`, '初始渲染时机'); //注册名必须带id 约定!
useDynamicAddEventCenter(pr, `${pr.data.id}-click`, '点击执行时机');
useDynamicAddEventCenter 第一个参数是 render 的四个参数组成的对象。第二个参数是注册的时机名,必须跟 id 相关,这是约定,否则多个组件可能会导致名称冲突,并且方便查找该时机。
注册完时机后,我们需要将时机放入对应的触发位置上,比如这个 button 的点击执行时机就放到 onclick 中:
<Button
onClick={() => {
eventCenter.runEventQueue(`${pr.data.id}-click`, pr.config);
}}
>
x.dooring
</Button>
其中第一个参数则为注册的时机名,第二个为 render 函数中最后一个参数 config
- 函数注册
函数由组件抛出,可以加载到事件链上。比如,注册个改变文本函数,那么我可以在任意组件的时机中去调用该函数,从而触发该组件改变文本。
函数注册需要放入 useEffect 中,在组件卸载时需要卸载函数!否则会导致函数越来越多。
useEffect(() => {
const functionCenter = eventCenter.getFunctionCenter();
const unregist = functionCenter.register(
`${pr.data.id}+改变文本函数`,
async (ctx, next, config, args, _eventList, iname) => {
const userSelect = iname.data;
const ctxVal = changeUserValue(
userSelect['改变文本数据源'],
args,
'_changeval',
config,
ctx
);
const text = ctxVal[0];
setText(text);
next();
},
[
{
name: '改变文本数据源',
data: ['ctx', 'input', 'dataSource'],
options: {
receive: '_changeval',
multi: false,
},
},
]
);
return () => {
unregist();
};
}, []);
函数中参数与配置见后面的函数开发。
4.5 右侧面板开发
为了开发自定义的右侧属性面板,我们只要将开发的组件配成一个对象放入 initFormComponents 即可。为了良好的开发体验,需要定义个 formMap 类型:
export interface FormBaseType {
receive?: string;
}
export interface FormInputType extends FormBaseType {
label: string;
}
export interface FormActionButtonType {}
export interface FormAnimateControlType {}
export interface FormMap {
input: FormInputType;
actionButton: FormActionButtonType;
animateControl: FormAnimateControlType;
}
formMap 的键名就是 initFormComponents 键名,formMap 的值对应组件需要收到的值。
以 input 组件为例,FormInputType 此时有2个属性: label, receive。
那么在开发该组件时,props 会收到:
interface MInputProps {
data: CreateOptionsRes<FormMap, 'input'>;
current: IBlockType;
config: UserConfig;
}
也就是 data 是 formMap 类型,而 current 是当前点击的组件,config 就不用说了。
还记得在左侧组件开发中的第三个参数吗?这样就都关联起来了:
style: [
createPannelOptions<FormMap, 'input'>('input', {
receive: 'text',
label: '文字'
})
],
createPannelOptions 这个函数的泛型里填入对应的组件,将会给收到的配置项良好的提示。
在配置项组件里所要做的就是接收组件传来的配置项,然后去修改 current 的属性:
function MInput(props: MInputProps) {
const option = useMemo(() => {
return props.data?.option || {};
}, [props.data]);
return (
<Row style={{ padding: '10px 20px' }}>
<Col span={6} style={{ lineHeight: '30px' }}>
{(option as any)?.label || '文字'}:
</Col>
<Col span={18}>
<Input
value={props.current.props[(option as any).receive] || ''}
onChange={(e) => {
const receive = (option as any).receive;
const clonedata = deepCopy(store.getData());
const newblock = clonedata.block.map((v: IBlockType) => {
if (v.id === props.current.id) {
v.props[receive] = e.target.value;
}
return v;
});
store.setData({ ...clonedata, block: [...newblock] });
}}
></Input>
</Col>
</Row>
);
}
由于可以很轻松的拿到 store,所以可以在任意地方进行修改数据。
将组件的 value 关联 current 的属性,onChange 去修改 store,这样就完成了个双向绑定。
注意:如果你的右侧组件需要用到 block 以外的属性,可能需要去判断是否处于弹窗模式。
4.6 自定义右键菜单
右键菜单可以进行自定义:
// 自定义右键
const contextMenuState = config.getContextMenuState();
const unmountContextMenu = contextMenuState.unmountContextMenu;
const commander = config.getCommanderRegister();
const ContextMenu = () => {
const handleclick = () => {
unmountContextMenu();
};
const forceUpdate = useState(0)[1];
contextMenuState.forceUpdate = () => {
forceUpdate((pre) => pre + 1);
};
return (
<div
style={{
left: contextMenuState.left,
top: contextMenuState.top,
position: 'fixed',
background: 'rgb(24, 23, 23)',
}}
>
<div
style={{ width: '100%' }}
onClick={() => {
commander.exec('redo');
handleclick();
}}
>
<Button>自定义</Button>
</div>
</div>
);
};
contextMenuState.contextMenu = <ContextMenu></ContextMenu>;
先拿到 contextMenuState,contextMenuState 上有个 unmountContextMenu 是关闭右键菜单方法。所以在点击后需要调用关闭。同时上面的 left 和 top 是右键的位置。另外,我们还需要在组件内增加强刷,赋值给 forceUpdate,用于在组件移动时进行跟随。
4.7 表单验证提交思路
表单验证提交有非常多的做法,因为数据全部是联通的,或者直接写个表单组件也可以。在不使用表单组件时,简单的做法是为每个输入组件做个验证函数与提交函数。这样是否验证就取决于用户的选取,而抛出的输入可以让用户选择放到哪,并由用户去命名变量。
在点击提交按钮时,调用所有组件的验证函数与提交函数,使其抛给上下文,再通过上下文聚合函数聚合成对象,最后可以通过发送函数发送给对应后端,从而完成整个流程。我们可以在 dooringx 中试下这个demo。
另一种方式是可以专门写个提交按钮,固定了参数,以及部分规则,比如规定在页面中的所有表单都会被收集提交。
那么我们可以利用数据源,将所有表单输出内容自动提交给数据源,最后的提交按钮按数据源规定格式的key 提取,发送给后端。
后期规划
后期我们还会在产品功能方面持续迭代优化,如果大家有好的建议, 也可以随时和我们交流, 也欢迎在 github 上积极提 issue。
如果大家对可视化搭建或者低代码/零代码感兴趣,也可以参考我往期的文章或者在评论区交流你的想法和心得,欢迎一起探索前端真正的技术。
github: dooringx | 快速高效搭建可视化拖拽平台
团队:Dooring可视化团队
更多推荐
- 如何设计可视化搭建平台的组件商店?
- 从零设计可视化大屏搭建引擎
- 从零使用electron搭建桌面端可视化编辑器Dooring
- (低代码)可视化搭建平台数据源设计剖析
- 从零搭建一款PC页面编辑器PC-Dooring
- 如何搭积木式的快速开发H5页面?
相关推荐
- 十分钟让你学会LNMP架构负载均衡(impala负载均衡)
-
业务架构、应用架构、数据架构和技术架构一、几个基本概念1、pv值pv值(pageviews):页面的浏览量概念:一个网站的所有页面,在一天内,被浏览的总次数。(大型网站通常是上千万的级别)2、u...
- AGV仓储机器人调度系统架构(agv物流机器人)
-
系统架构层次划分采用分层模块化设计,分为以下五层:1.1用户接口层功能:提供人机交互界面(Web/桌面端),支持任务下发、实时监控、数据可视化和报警管理。模块:任务管理面板:接收订单(如拣货、...
- 远程热部署在美团的落地实践(远程热点是什么意思)
-
Sonic是美团内部研发设计的一款用于热部署的IDEA插件,本文其实现原理及落地的一些技术细节。在阅读本文之前,建议大家先熟悉一下Spring源码、SpringMVC源码、SpringBoot...
- springboot搭建xxl-job(分布式任务调度系统)
-
一、部署xxl-job服务端下载xxl-job源码:https://gitee.com/xuxueli0323/xxl-job二、导入项目、创建xxl_job数据库、修改配置文件为自己的数据库三、启动...
- 大模型:使用vLLM和Ray分布式部署推理应用
-
一、vLLM:面向大模型的高效推理框架1.核心特点专为推理优化:专注于大模型(如GPT-3、LLaMA)的高吞吐量、低延迟推理。关键技术:PagedAttention:类似操作系统内存分页管理,将K...
- 国产开源之光【分布式工作流调度系统】:DolphinScheduler
-
DolphinScheduler是一个开源的分布式工作流调度系统,旨在帮助用户以可靠、高效和可扩展的方式管理和调度大规模的数据处理工作流。它支持以图形化方式定义和管理工作流,提供了丰富的调度功能和监控...
- 简单可靠高效的分布式任务队列系统
-
#记录我的2024#大家好,又见面了,我是GitHub精选君!背景介绍在系统访问量逐渐增大,高并发、分布式系统成为了企业技术架构升级的必由之路。在这样的背景下,异步任务队列扮演着至关重要的角色,...
- 虚拟服务器之间如何分布式运行?(虚拟服务器部署)
-
在云计算和虚拟化技术快速发展的今天,传统“单机单任务”的服务器架构早已难以满足现代业务对高并发、高可用、弹性伸缩和容错容灾的严苛要求。分布式系统应运而生,并成为支撑各类互联网平台、企业信息系统和A...
- 一文掌握 XXL-Job 的 6 大核心组件
-
XXL-Job是一个分布式任务调度平台,其核心组件主要包括以下部分,各组件相互协作实现高效的任务调度与管理:1.调度注册中心(RegistryCenter)作用:负责管理调度器(Schedule...
- 京东大佬问我,SpringBoot中如何做延迟队列?单机与分布式如何做?
-
京东大佬问我,SpringBoot中如何做延迟队列?单机如何做?分布式如何做呢?并给出案例与代码分析。嗯,用户问的是在SpringBoot中如何实现延迟队列,单机和分布式环境下分别怎么做。这个问题其实...
- 企业级项目组件选型(一)分布式任务调度平台
-
官网地址:https://www.xuxueli.com/xxl-job/能力介绍架构图安全性为提升系统安全性,调度中心和执行器进行安全性校验,双方AccessToken匹配才允许通讯;调度中心和执...
- python多进程的分布式任务调度应用场景及示例
-
多进程的分布式任务调度可以应用于以下场景:分布式爬虫:importmultiprocessingimportrequestsdefcrawl(url):response=re...
- SpringBoot整合ElasticJob实现分布式任务调度
-
介绍ElasticJob是面向互联网生态和海量任务的分布式调度解决方案,由两个相互独立的子项目ElasticJob-Lite和ElasticJob-Cloud组成。它通过弹性调度、资源管控、...
- 分布式可视化 DAG 任务调度系统 Taier 的整体流程分析
-
Taier作为袋鼠云的开源项目之一,是一个分布式可视化的DAG任务调度系统。旨在降低ETL开发成本,提高大数据平台稳定性,让大数据开发人员可以在Taier直接进行业务逻辑的开发,而不用关...
- SpringBoot任务调度:@Scheduled与TaskExecutor全面解析
-
一、任务调度基础概念1.1什么是任务调度任务调度是指按照预定的时间计划或特定条件自动执行任务的过程。在现代应用开发中,任务调度扮演着至关重要的角色,它使得开发者能够自动化处理周期性任务、定时任务和异...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- MVC框架 (46)
- spring框架 (46)
- 框架图 (58)
- flask框架 (53)
- quartz框架 (51)
- abp框架 (47)
- jpa框架 (47)
- laravel框架 (46)
- springmvc框架 (49)
- 分布式事务框架 (65)
- scrapy框架 (56)
- shiro框架 (61)
- 定时任务框架 (56)
- java日志框架 (61)
- JAVA集合框架 (47)
- grpc框架 (55)
- ppt框架 (48)
- 内联框架 (52)
- winform框架 (46)
- gui框架 (44)
- cad怎么画框架 (58)
- ps怎么画框架 (47)
- ssm框架实现登录注册 (49)
- oracle字符串长度 (48)
- oracle提交事务 (47)