低代码: 开发难点分析,核心技术架构设计

news2024/12/29 10:18:44

开发难点分析


1 )怎样实现组件

  • 核心问题:编辑器 和 页面其实整个就是一系列元素构成的
  • 这些元素的自然应该抽象成组件,这些组件的属性应该怎样设计
  • 在不同的项目中怎样做到统一的使用

2 )跨项目使用

  • 在不同的项目中怎样做到统一的使用

3 )组件的可扩展性

  • 组件的可扩展性,虽然在需求中我们只要求了三种组件
  • 但是最初的设计是否能够具有良好的可扩展性

4 )编辑器的整体状态

  • 说完了组件作为个体的问题,现在把它融合到编辑器页面来讨论
  • 编辑器做的功能其实就是对一系列组件增删改的操作
  • 所以怎样设计编辑器的整体状态

5 )增加和删除

  • 说完了组件作为个体的问题,现在把它融合到编辑器页面来讨论
  • 编辑器做的功能其实就是对一系列组件增删改的操作
  • 所以怎样设计编辑器的整体状态

6 )属性渲染成表单

  • 组件有多种,它的属性也有多种
  • 1 怎样将这些属性渲染成不同的表单组件(也有可能不仅仅是表单组件)
  • 2 在表单组件中,属性作出修改以后,怎样实时的将值反射到组件中去

7 )实时的反馈

  • 在编辑器中通过属性修改,如何同步到页面

8 )插件化

  • 编辑器有很多的交互:拖动移动位置,拖动改变大小,快捷键,右键菜单,缩放,重做/回滚等等功能
  • 它们都是在核心问题之外的交互,那么很自然,我们是否能将这些功能进行解耦?

核心技术架构


1 )项目拆分

基于上述分析,我们可以把项目拆分成不同的项目

1.1 前端-B端系统仓库

  • 可以采用简单的SPA模型,基于 Vue 或 React
  • 这里可以集成下面的编辑器项目,但是会成为一个巨石仓库
  • 建议采用微前端的模式,集成下面的编辑器项目

1.2 前端-编辑器

  • 用于实现复杂的编辑器项目
  • 可以再上述B端中耦合,但推荐使用微前端拆分出来单独成一个项目

1.3 前端-组件库项目

  • 复用于 B端,C端(包含移动端)

1.4 后端项目

  • 基于 Restful API 提供接口服务
  • 基于SSR 提供H5页面

2 )项目间的关系

3 ) 技术方案设计注意事项

  • 技术方案设计,为的就是寻找一个方向,论证:可行性、扩展性、复杂度高低。
  • 不要一直沉浸在细节里,要站在上帝的视角来看问题

4 ) 核心问题分析与设计

4.1 业务组件库实现方案

设计原则

  • 业务组件库大多数都是展示型组件
  • 其实就是把对应的 template 加上属性(大部分是 css 属性)展示出来
  • 会有少量行为,比如点击跳转等
  • 而且这些组件会在多个不同的端进行展示,所以业务组件库就是从简的原则
  • 必须避免和编辑器编辑流程的耦合

组件命名

  • 使用一个字母(X)加组件的名称:比如 XText 或 x-text
  • 这个 X 可以是你的域名,公司名,部门名等有意义的名称

属性设计

  • 属性其实可以分为两大类
  • 伪代码
    // 方案一,将 css 作为一个统一的对象传入
    <XText
    	css={{color: '#fff' ...}}
    	text="nihao"
    />
    // 内部实现比较简单
    <p style={props.css}></p>
    
    // 方案二,将 所有属性全部平铺传入
    <XText
    	:text="nihao"
    	:color="#fff"
      ...
    />
    // 内部实现会复杂一点
    const styles = stylePick(props)
    <p style={styles}></p>
    
  • 方案一内部实现简单,但是保存的时候要多一层结构
    • 并且更新数据的时候要知道是样式还是其他属性
  • 方案二 内部实现稍微复杂一点,但是保存简单,更新数据不需要在做辨别
    • 这些组件目前有一些共有的属性,称之为公共属性
    • 提到公共属性我们就要注意代码重用的问题
  • 点击跳转伪代码
    // 比如 在 Xtext 和 XImage 中都点击跳转的功能,属于公共属性的行为
    // 抽象出一些通用的函数,在组件中完成通用的功能
    import useClick from 'useClick'
    
    useClick(props)
    

4.2 编辑器细节问题

4.2.1页面的组成

  • 可能存在背景的设定:由图片或者纯色组成
  • 关于页面的元素
    • 由各种不同的元素(组件)组成。在这里面有属性的配置
      • 一部分属性界定它的位置(position)
      • 一部分属性界定它的展示(looks),比如宽高,内容等

4.2.2 由此,我们可以设计相关JSON数据模型

{
	"page": {
		"title": "作品1""props":{
			"backgroundImage":"",
			"backgroundPostion": """backgroundMusic":"2.mp3", // 可能有的网页背景音乐
			"backgroundMusicAutoStart": "true" // 背景音乐是否可以自动播放
			//...
		},
	}
	// 该采用对象还是数组?
	"components":[
		{
			//指代对应的组件类型-可以和公共组件库的组件命名对应
			"name": "text",
			//要有特殊的ID,因为每个组件都是独特的,需要使用ID筛选组件
			"id":"1",
			"props": {
				//位置属性
				"left": "10px", "top": "10px",
				//展示属性
				"text": "hello world", "fontSize": "16px"
			},
		},
		{
			"name": "image",
			"id": "2",
			"props": {
				"left": "5px","top": "20px",
				"src": "cdn.url", "width": "100px", "height": "100px"
			},
		},
		{
			"name":"date", // 日期组件
			"props": {
				"left": "5px", "top": "20px",
				"date": "now", "width": "100px", "height": "100px", "style":1
			}
		}
	]
}

4.2.3 数据的流转

  • 向画布添加组件或者删除组件(向 components 数组添加或者删除特定的组件)
  • 更新组件的某个属性 (找到对应的 component,然后更新它的 props)
  • 渲染画布或者作品(循环保存的作品信息,使用每个组件特定的属性进行渲染)

4.2.4 编辑器的设计

  • 在实现工程中,我们完成了表单和数据的对应其实完成了一个高层次的抽象,用数据描述界面

  • 对比阿里的项目 form-render 现在更名为 x-render, 或访问 xrender.fun 这个开源工具其实和我们的思路是一样的

  • 编辑器页面主要有三个部分,为左中右结构,左侧为组件模版库,中间为画布,右侧是设置面板

    • 左侧是预设各种组件模版并进行添加
    • 中间是使用交互的手段更新元素的值
    • 右侧是使用表单的手段更新元素的值
  • 注意:和后端相关的暂不讨论 - 预览 发布 等

整体状态设计

  • 不难看出我们的编辑器其实就是围绕着中间画布的元素来进行一系列操作
  • 那么自然而然着是一系列的元素组成的
  • 我们应把它抽象成一系列拥有特定数据结构的数组
  • 伪代码
    export interface GlobalDataProps {
      // 供中间编辑器渲染的数组
      components: ComponentData[];
      // 当前编辑的是哪个元素,uuid
      currentElement: string;
      // 当然最后保存的时候还有有一些项目信息,这里并没有写出,等做到的时候再补充
    }
    interface ComponentData {
      // 这个元素的 属性,属性请详见下面
      props: { [key: string]: any };
      // id,uuid v4 生成
      id: string;
      // 业务组件库名称 x-text,x-image 等等 
      name: string;
    }
    

场景设计

  • 根据上面的数据结构我们可以针对不同的需求进行技术方案设计
    • 将元素渲染到画布
      • 使用 store 中 compoents 当中的数据,循环渲染输出组件即可
        compoents.map(component => <component.name {...props} />
    • 渲染左侧预设组件模版
      • 原理和上面一样的,只不过数据是预设好的,这个可以写死在本地,也可以从服务器端取得。
      • 他们和中间元素不一样的是,这些组件都有一个点击事件,我们可以添加一层 wrapper 来解决这个问题。
      • 这样也可以和内部的 components 做到隔离,互不影响
        compoents.map(component => <Wrapper><component.name {...props} /></Wrapper>
    • 添加和删除组件
      • 非常简单的逻辑,向 store 中添加和删除组件即可。
        // 添加
        components.push({type: '', props: {} })
        // 删除
        components = components.filter((component) => component.id !== id)
        

编辑组件设计

  • 将属性映射到表单

    • 点击画布中的某个组件需要将该元素的属性以不同表单的形式展示到右侧
    • 几个典型场景的实现,大家应该发现,其实没那么复杂
    • 就是对全局状态中的 components 字段进行修改而已
    • 现在我用一张更大的图,来描述应用的整个流程
    • 通常方案, 将这些表单组件写死到页面中去 (不推荐)
      {
        text: '123'
        color: '#fff'
      }
      
      <input value={text}/>
      <color-picker value={color}/>
      ...
      
      • 这样写非常简单,但是后期遇到的问题也会非常多,比如代码会非常冗余
      • 对不同类型的业务组件都要做一大堆判断,可扩展性很差
      • 对新的业务组件都要加一堆代码等等
    • 抽象遍历方案
      • 看到界面展示,应该想到另外一个纬度,界面UI 其实就是数据的抽象
      • 所以我们自然想到的就是使用特定的数据结构将它渲染成界面
        const textComponentProps = {
          text: 'hello',
          color: '#fff'
        }
        
        interface PropsToForm {
          component: String;
        }
        const propsMap = {
          text: {
            component: 'input'
          },
          color: {
            component: 'color-picker'
          }
        }
        
        // 这里我们还是循环所有属性,在每个属性中渲染对应处理这个属性的组件
        map(textComponentProps, (key, value) => {
          <propsMap[key].component value={value}/>
        })
        
        • 当遇到没有类似的 Form 组件的时候,我们可以进行二次开发
        • 只要这个组件有 value 的对应属性
        • 这在一定程度上还满足了 可扩展性这个命题,组件的属性可以扩展
        • 对于 color 这个属性,我们自己开发一个取色器或者二次封装一个取色器组件
        • 只要传入 value 属性即可
  • 更新表单将数据更新到属性

    • 我们的数据流始终保持自上而下的顺序
    • 也就是说表单更新最终要反射回到总体的 store 当中去
    • 这个时候我们在对应的组件当中发射出一个事件,change,当 change 发生的时候
    • 我们能够知道是哪个元素的哪个属性,以及新的值是什么,我们就用这些信息更新这个值
    • 这样 store 完成更新,元素的 props 发生更新,那么整个数据流动就完成了
      map(textComponentProps, (key, value) => {
        const handleChange = (propKey, newValue, id) => {
          const updatedComponent = store.components.find(component.id === id)
          updatedComponent.props[propKey] = newValue
        }
        <propsMap[key] value={value} @change={handleChange}>
      }
      
  • 更新表单将数据更新到属性

    • 我们的数据流始终保持自上而下的顺序

    • 也就是说表单更新最终要反射回到总体的 store 当中去

    • 这个时候我们在对应的组件当中发射出一个事件,change

    • 当 change 发生的时候,我们能够知道是哪个元素的哪个属性

    • 以及新的值是什么,我们就用这些信息更新这个值

    • 这样 store 完成更新,元素的 props 发生更新,那么整个数据流动就完成了

      map(textComponentProps, (key, value) => {
        const handleChange = (propKey, newValue, id) => {
          const updatedComponent = store.components.find(component.id === id)
          updatedComponent.props[propKey] = newValue
        }
        <propsMap[key] value={value} @change={handleChange}>
      }
      
    • 除了表单的更新,还要说一下画布中的交互更新

    • 其实画布中的更新也是采用发射事件的方式对store 的某些值进行更新

    • 比如说拖动改变位置,最终拖动的过程中也是触发对应的change 事件去用相同的逻辑对值进行更新

    • 这里也要注意,我们需要在业务组件外层,添加一个Wrapper

    • 各种事件都是放在这个 Wrapper 上面的,比如支持拖动,改变位置后发送 change 事件

    • 对于复杂组件也是如此,不管你内部的逻辑有多复杂,添加上传图片,删除,编辑

    • 最终发送出来的事件里面的值,就是对这个 pictures 的值的变换

    • 比如多加了一张照片,那就是数组中的值变成了三项

  • 业务组件可扩展性

    • 属性映射到表单:更复杂的业务组件也只不过是对应各种新的属性而已
    • 可以用现有的 form 对应组件或者是自研或者二次包装的组件对其进行处理
      // 假如有复杂组件 - 比如说是幻灯片
      {
        pictures: ['1.jpg', '2.jpg']
      }
      
      {
        pictures: {
          component: 'pics-processer'
        }
      }
      // 我们自己开发一个 图片处理的 组件即可,传入 value ,这个数组,他应该会渲染出一系列的图片显示
      
    • 表单修改后更新
    • 不管你内部有的逻辑有多复杂,添加上传图片,删除,编辑,最终发送出来的事件里面的值
    • 就是对这个 pictures 的值的变换,比如多加了一张照片,那就是数组中的值变成了三项
  • 画布插件化

    • 比如快捷健,他只写成普通的可重用的函数即可,提供回调即可
    • 在回调中,我们可以对全局 store 进行一系列的改写
    • 而快捷键这个功能和编辑器是没有任何关系的
      // 伪代码
      useHotKey()
      useContextMenu()
      ...                                                                                                                                                       
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1982947.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最强开源文生图模型一夜易主!SD一作、Stabililty AI核心成员Robin Rombach下场创业了,一出手就是王炸。

时隔4个月&#xff0c;开源文生图模型霸主Stable Diffusion原班人马再创业&#xff01;2024年8月1日官宣&#xff1a;Black Forest Labs成立&#xff0c;公司的第一个产品FLUX.1系列模型包含专业版、开发者版、快速版三种模型&#xff0c;效果直接秒杀Midjourney、DALL-E和Stab…

解决报错:AssertionError: Torch not compiled with CUDA enabled

首先查看自己的cuda是否可用 torch.cuda.is_available()这里我的cuda是不适配torch的&#xff0c;所以需要重新安装适配的torch 查看自己的cuda版本 方法1 方法2 在cmd处输入nvidia-smi 这样可以找到的自己的CUDA版本安装符合自己版本的pytorch 进入pytorch官网https://pyt…

双指针实现删除字符串中的所有相邻重复项

class Solution:def removeDuplicates(self, s: str) -> str:res list(s)slow fast 0length len(res)while fast < length:# 如果一样直接换&#xff0c;不一样会把后面的填在slow的位置res[slow] res[fast]# 如果发现和前一个一样&#xff0c;就退一格指针if slow …

app逆向实战:某监管app2.0.5版本ROOT检测绕过

本篇博客旨在记录学习过程&#xff0c;不可用于商用等其它途径 场景 如下图&#xff0c;在我们打开APP时页面提示如此样式说明被检测到ROOT了&#xff0c;这种情况下无法进入页面请求抓包。 查壳 如果这个APP没有加固&#xff0c;那我们可以通过反编译修改检测的代码或者F…

Django与数据库

目录 创建项目app 路由子表 数据库 创建数据库 什么是ORM 定义数据库表 Django Admin 管理数据 过滤条件 代码直接生成HTML 使用模板 前后端分离架构 对资源的增删改查处理 列出客户 添加客户 临时取消 CSRF 校验 修改客户信息 删除客户 Django中ORM的处理 数据模…

QThread::wait: Thread tried to wait on itself

调用QThread的wait()方法时&#xff0c;报警&#xff1a;QThread::wait: Thread tried to wait on itself 原因&#xff1a;在自己的线程中调用线程的wait()方法。 解决方法&#xff1a;在线程外的其他线程中&#xff0c;调用线程的wait()方法。 示例代码如下&#xff1a; …

MATLAB学习之绘图篇(二维图)

目录 1.1基础图形绘制 1.1.1使用plot函数进行图形绘制 1.1.2为图像增加图例 1.1.3为图片增加标题以及坐标轴的描述 1.1.4控制坐标轴&#xff0c;边框以及网络 1.1.5在一个图像上绘制多条曲线 1.1.6在一个窗口绘制多个图像 1.1.7对图形的对象进行操作( 坐标属性&#xff…

LeetCode Hot100 二叉搜索树中第K小的元素

给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,1,4,null,2], k 1 输出&#xff1a;1示例 2&#xff1a; 输入&#xf…

【C/C++】C语言和C++实现Stack(栈)对比

我们初步了解了C&#xff0c;也用C语言实现过栈&#xff0c;就我们当前所更新过的有关C学习内容以栈为例子&#xff0c;来简单对比一下C语言和C。 1.C中栈的实现 栈的C语言实现在【数据结构】栈的概念、结构和实现详解-CSDN博客 &#xff0c;下面是C实现的栈&#xff0c; 在St…

ImageNet数据集和CIFAR-10数据集

一、为什么需要大量数据集 人工智能其实就是大数据的时代&#xff0c;无论是目标检测、图像分类、还是现在植入我们生活的推荐系统&#xff0c;“喂入”神经网络的数据越多&#xff0c;则识别效果越好、分类越准确。因此开源大型数据集的研究团队为人工智能的发展做了大量贡献…

QT教程-十五,Qt-5.14.2安卓开发环境配

目录 一&#xff0c;Qt需要的组件 二&#xff0c;需要的环境配置 1,JDK配置 1.1 配置JDK环境 1.2 Qt中配置JDK,SDK,NDK 2,创建Qt安卓项目 2.1 配置gradle-5.5.1-bin.zip文件 最近想开发一款安卓app应用&#xff0c;但是又不想去重新学习一个新的知识体系。于是在自己更为…

2024.8.5 作业

1> 使用有名管道实现&#xff0c;一个进程用于给另一个进程发消息&#xff0c;另一个进程收到消息后&#xff0c;展示到终端上&#xff0c;并且将消息保存到文件上一份 create.c #include <myhead.h> int main(int argc,const char *argv[]) {if(mkfifo("./lin…

在windows下生成的mac苹果电脑端可以执行的unity项目程序,在mac电脑不能执行的修改方法

在windows下开发Unity项目&#xff0c;如果要执行的电脑是mac&#xff0c;必须在windows下生成for mac的程序&#xff0c;发现拷贝到mac电脑后不能执行&#xff1a; 原因是改程序没有mac的执行权限: 修改方法&#xff1a; 先打开终端&#xff1a; 进入文件所在目录 cd Downlo…

✅【文献串读】Object Counting论文串读

get宝藏博主&#xff1a;Tags - 郑之杰的个人网站 (0809zheng.github.io) 目标计数(Object Counting) - 郑之杰的个人网站 (0809zheng.github.io) 目录 1.《CountGD: Multi-Modal Open-World Counting》 2.&#xff08;2024CVPR&#xff09;《DAVE – A Detect-and-Verif…

报表控件stimulsoft操作:使用 Stimulsoft 产品连接到 OData 源

Stimulsoft Ultimate &#xff08;原Stimulsoft Reports.Ultimate&#xff09;是用于创建报表和仪表板的通用工具集。该产品包括用于WinForms、ASP.NET、.NET Core、JavaScript、WPF、PHP、Java和其他环境的完整工具集。无需比较产品功能&#xff0c;Stimulsoft Ultimate包含了…

FFmpeg实战 - 解复用与解码

大纲目录 文章目录 前置知识音视频基础概念解复用、解码的流程分析FFMPEG有8个常用库 常见音视频格式的介绍aac格式介绍&#xff08;ADTS&#xff09;h264格式分析FLV和MP4格式介绍 FFmpeg解码解封装实战数据包和数据帧&#xff08;AVPacket/AVFrame&#xff09;AVPacket/AVFra…

VHDX 安装操作系统

前言 使用 Win11 作为主力系统&#xff0c;再通过 VHDX 虚拟硬盘来安装另外的 Windows 系统。使用 VHDX 安装系统的好处在于&#xff1a;不影响原系统&#xff0c;用完即删。 需求 安装双系统&#xff0c;使用 VHDX 安装 WinServer 2022。 操作步骤 创建 VHDX 打开磁盘管…

一道笔试题 - 无重复字符的最长子串

老生常谈的一道题&#xff0c;常见并 文章目录 描述预期结果Java代码 描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的最长子串的长度。 预期结果 Java代码 import java.util.HashSet; import java.util.Set;public class Demo2 {public static void main(S…

为什么回测效果非常好的策略实盘却不行?

这是一个絮絮叨叨的专题系列&#xff0c;跟大伙儿唠一唠量化相关的小问题&#xff0c;有感而发写到哪算哪&#xff0c;这是第二期&#xff0c;来唠个12块钱的~ 之前在某乎看到这个问题&#xff0c;说的是自己的MACD策略回测绩效不错&#xff0c;但实盘比较拉胯&#xff0c;希望…

pxe666666

1.下载图形化工具 2.init 5进入 3.配个ip 4.安装图形化生成kickstart自动安装脚本的工具 5.配置httpd 6.浏览器查看 7.设置 保存 8.检查有无问题 9.共享 10.测试 11编辑配置文件37及后的脚本&#xff0c;并注释掉 27 28 12.安装pxe 13.共享pxelinux.0数据文件的网络服务 14.查询…