Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

前端可视化搭建二三事 #26

Open
SunXinFei opened this issue May 14, 2020 · 4 comments
Open

前端可视化搭建二三事 #26

SunXinFei opened this issue May 14, 2020 · 4 comments

Comments

@SunXinFei
Copy link
Owner

SunXinFei commented May 14, 2020

先说下大前端与前端赋能,这里有一篇winter的文章,里面有一个观点很有趣,就是大前端并不是抢其他工种业务,而是一种赋能,那我们总结下前端赋能的案例,进行理解:
市面上有效赋能的产品或工具:

  • 赋能前端:Vue、React、webpack、vscode、Antd、AntV、飞冰
  • 赋能后端:阿里的云凤蝶、飞冰
  • 赋能设计师、产品:云凤蝶、墨刀
  • 赋能运营:可视化搭建H5活动页
@SunXinFei
Copy link
Owner Author

SunXinFei commented May 25, 2020

可视化搭建的前世今生

页面可视化搭建,最早可以追溯很远,耳熟能详的就是Dreamweaver,以及后来基于jquery等前端搭建的项目,随着web开发的发展,前后端分离与数据驱动DOM动态页面,逐渐成为历史。

页面构成

我们认为一个页面简单的说是由HTML结构与数据组成静态页面,配合一些逻辑变成为动态页面。(css样式,在这里我们归入Data一类中)
image

页面组件化

image
页面经过渲染之后,是 HTML 元素构成的树形结构,其中的结点便是我们前端经常提到的组件,主要实现了功能封装和可复用的两个功能。数据来源主要为内部维护变量和外部传入变量。
附:组件化还是要重提,主要是因为组件化的存在,才让现在的搭建器发挥得更优。

落地页开发痛点

活动页面特点

前端业务中, 经常需要开发产品介绍页/营销页/活动页/图片展示页等页面. 这类需求有以下几个特点:
页面类似: 同行业的落地页页面布局和业务逻辑较固定.
需求高频: 不同的人员每周会有多个这种需求.
迭代快速: 开发时间相对较短, 上线时间紧.
开发性价比低: 开发任务重复, 俗称画页面,消耗各方的沟通时间和人力.

常规开发流程

流程:

  1. 运营/产品提出页面需求prd文档.
  2. 开发根据UI设计稿完成页面开发.
  3. 测试进行页面测试.
  4. 运维进行页面上线.
  5. 运营/产品进行线上验收.

痛点:

  • 方多参与, 反复沟通, 串行流程.
  • 页面上线周期长, 无法快速响应活动需求.
  • 人力陷入重复工作泥潭, 忙碌而低效.
    image

高效率的流程

image
流程

  1. 运营/产品提出页面需求prd文档.
  2. 运营/产品在搭建系统中选取合适的模板进行页面搭建.
  3. 页面自动化发布上线, 页面需求完成, 流程完结.
  4. 如果运营/产品没有找到合适的模板.
  5. 开发进行页面模板开发, 并将页面模板添加到搭建系统中.
    运营/产品继续流程2.
    随着搭建系统时间的推移,会沉淀出越来越多的组件/模块提供使用,对开发的依赖越来越低。

参考:
页面可视化搭建工具技术要点
页面可视化搭建工具前生今世

@SunXinFei
Copy link
Owner Author

SunXinFei commented May 26, 2020

关于搭建框架

目前一些旧的搭建平台,组件、搭建器、配置项,这三个耦合度非常高,导致后期拓展性很差。举例:当要添加一个例如视频之类的简单组件,如果需要动组件列表、搭建器、配置项这三个地方的代码,则可以断定耦合度非常高了。
引用一段话

任何一个有一定复杂度、会持续增长的应用最重视的,其实并不是开发速度,而是可维护性和可扩展性。 这也是框架设计者们摆在首位的事情。可扩展性的好坏取决于框架的扩展机制。在我们的上面的设计中需要扩展的有两部分,组件和功能。组件的扩展可以通过允许用户提交自定义组件来实现。功能的扩展主要由框架开发者完成。 --侯振宇

在文章页面可视化搭建工具技术要点中提到框架要解耦是最优解,而大部分搭建项目其实确实是服务于业务线,而导致react的落地页,搭建平台也是react语言,如果要实现框架与组件的解耦可行性也是有的,因为我们不管渲染层面是什么语言其实搭建器产生的都是一段段json数据,所以关注点又回到了搭建器的操作台的组件预览,是否需要用搭建器的语言再实现一遍,而且配置区域的数据变化就不能为redux或vuex的语法,从这两点考虑,框架与组件解耦就需要值得商榷了。但是后面随着微前端等前景的发展,未来还是可以完美实现的。
所以除了框架解耦不再讨论之外,下面会从组件和配置项两个纬度讨论解耦的事情。

可视化工具结构

我们以一个开源的工具为例:
image
其实可视化搭建工具的结构一般分成四块,如图所示,按照数字对应关系分别为:

  • 组件列表:可以使用的组件的列表,一般是组件的图片和文案展示;
  • 操作台/画布:进行组件拖拽布局、选择等交互操作;
  • 配置区域:配置选中组件的属性;
  • 功能按钮:用来保存、预览、redo、undo等操作;

组件列表

组件列表中的组件主要分为三类:

  • 基础/业务组件:原子类型的,比如:文本、图片、按钮、轮播图等;
  • 组合组件:由原子类型的组件拼接而成的json结构,相当于mini的模版;
  • 第三方组件:由他人开发完成,注册到组件市场;

关于组件市场的思想如下图:
image
所以组件市场中的组件和普通组件差别不大,属性配置按照规定的地方进行书写,渲染时动态加载第三方的js与css文件即可;

{
    "type": "comp-feed",
    "version": "0.0.22",
    "properties": {
        "title": {
            "type": "String",
            "name": "用户名",
            "value": "测试一版"
        },
        "avator": {
            "type": "Image",
            "name": "头像显示",
            "value": "https://s.gravatar.com/avatar/518515a45a5c15165f559857c3d60a95?size=100&default=retro",
            "canDelete": true
        }
    },
    "event": {
        "qrShow": 453849
    }
}
loadResources(docData) {
      let head = document.getElementsByTagName("head")[0];
      let promiseList = [];
      docData.pages.forEach(item => {
        (item.children || []).forEach(val => {
          // 异步组件
          if (val.compType !== 1 && val.type) {
            promiseList.push(
              new Promise(resolve => {
                let tmpSrc =`https://unpkg.com/ ${val.type}${
                  val.version ? `@${val.version}` : ""
                }/dist/js/index.js`;
                let script = document.createElement("script");
                script.type = "text/javascript";
                script.src = tmpSrc;
                script.onload = () => {
                  resolve();
                };
                head.appendChild(script);
              })
            );
            promiseList.push(
              new Promise(resolve => {
                let tmpSrc = `https://unpkg.com/ ${val.type}${
                  val.version ? `@${val.version}` : ""
                }/dist/css/index.css`;
                let link = document.createElement("link");
                link.href = tmpSrc;
                link.setAttribute("rel", "stylesheet");
                link.setAttribute("type", "text/css");
                link.onload = () => {
                  resolve();
                };
                head.appendChild(link);
              })
            )
          }
        });
      });
      Promise.all(promiseList).then(() => {
        this.pages = docData.pages || [];
      });
    }
上述代码自动加载如下的js和css文件
https://unpkg.com/comp-feed@0.1.3/dist/css/index.css
https://unpkg.com/comp-feed@0.1.3/dist/js/index.js

操作台/画布

这里主要集中以下操作技术:

  • 元素拖拽
  • 缩放与旋转
  • 辅助线计算
  • 对齐方式
  • 层级调整
  • 防止误操作
  • 右击菜单
  • 快捷键
  • redo/undo

多是基于组件data中top、left、height、width属性的对数据结构的遍历、计算与揉合,这里不多做赘述。

配置区域

整体思路就是配置区域对配置区域读/写,组件对配置数据读取,如下图:
image
而组件通过props传入的数据驱动渲染:
image
关于组件配置项的描述,为各组件差异化的配置数据定义数据结构和字段类型,我们这里用JSON表示, 因为其格式灵活,支持数据嵌套,前端友好。

image

如果为每个组件都编写一个表单页面,工作量较大;对于复杂的配置项,表单页面的编写工作量可能会大于页面组件的开发工作量。我们将配置项的颗粒度细化出来,比如配置项是"Text",我们就渲染出一个Input框,如果配置项是“Color”,我们则渲染出一个"Colorpicker",这个思想在JsonEditor云凤蝶项目中得到体现。
我们将配置项进行原子性的抽离,这样就会得到:

  • 简单的数据类型:Boolean、Color、Date、Image、Number、Text等;
  • 复杂数据类型:Array、Object;

复杂数据类型和简单数据类型嵌套,基本可以满足大部分组件的配置。当然在实际的业务中可能出现一些特殊的要求或者UI设计师设计出特殊的配置项样式,我们可以将这一类归为业务数据类型,比如字体配置:Font,开发者单独开发,由于组件和配置项借助schema.json配置项解耦,独立开发Font的配置即可,配置项可插拔状态,拓展性很好,完全不依赖组件。
image

云凤蝶Schema
JsonSchema

实时预览(兼容手机端)

现在预览状态兼容手机端展示主要是两种:
一种是百分比:百分比的这种目前遇到的不多,出现的场景一般是画布中每行元素固定,也就是画布中元素不支持任意位置拖拽;
一种是px转rem:px转rem这种方式目前比较常见,元素位置为absolute,将data中存储的px数值,计算为对应的rem值即可。但是注意一点,如果是fix的元素,在pc预览时,如果给内容区域设定了最大宽度,如下图的样式:
image
这时候,就需要写一些特殊的代码来兼容fix属性的模块了:

//fix模块css的left计算
getLeft() {
      if (document.body.offsetWidth > 750) {
        return this.data.css.left + (document.body.offsetWidth - 750) / 4;
      } else {
        return this.data.css.left;
      }
    },
//fix模块计算上or下fix位置
    getTopOrBottom(data) {
      if (data.properties.fixPos == "top") {
        return `${data.css.top / 50}rem`;
      } else {
        return `${-data.css.top / 50}rem`;
      }
    }

同理在搭建操作台渲染fix类型的组件时,也需要特殊处理。

@SunXinFei
Copy link
Owner Author

相似项目汇总

行业竞品(排名不分先后)

  • 头条巨量引擎官方落地页制作工具:橙子建站
  • 阿里SaaS 模式的智能建站平台:云凤蝶
  • 百度H5: https://h5.bce.baidu.com/
  • 基于智能内容创意设计:易企秀
  • 页面搭建界中的PhotoShop:ih5
  • 美团外卖前端可视化界面组装平台 —— 乐高

开源系列:

form表单开源系列:

@SunXinFei
Copy link
Owner Author

补充-关于联动

{
  tmpSelect:{
    type:'SelectRadio',
    config:{
      options:[{
        id:'1',
        name:'aaa'
      },{
        id:'2',
        name:'bbb'
      }]
    },
    value:'1'
  },
  form1:{
    type: 'Input',
    config:{
      name:'表单1'
    },
    value:'表单value值',
    dev:{//联动控制
      show:{//控制是否展示 也可以为disabled等控制
        param1:{//变量
          filed:'tmpSelect',
          eval:'===',
          value:'1'
        },
        param2:{
          filed:'tmpSelect',
          eval:'!==',
          value:'2'
        },
        logic:' %{param1}% || %{param2}%' //条件:tmpSelect === 1 || tmpSelect !==2才展示
      }
    }
  }
}

如上示例,我们有一个tmpSelect的组件,组件表现形式是SelectRadio,有aaa和bbb两个选项,如果想通过tmpSelect来控制form1是否展示,则给form1添加控制属性dev,dev中有show/disabled等组件状态控制。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant