如何设计高扩展的在线网页制做平台

如何设计高扩展的在线网页制做平台

本文主要介绍如何设计一个高扩展的在线网页制做平台,会交代一些背景和最终的效果以及核心设计方案。javascript

背景

2018年3月份开始,随着运满满的快速发展,开始在频繁的迭代各类活动,那时最快的方式就是拷贝老的活动项目,而后按需求修改,接着上线,然而这种方式很快就遇到了瓶颈,迫使运营团队也会去寻找一些第三方平台去知足本身的运营要求,不过因为定制化弱和用户信息没打通致使没办法大量使用,仍是只能等待前端资源排期,两个比较突出的问题。css

  1. 产品每一个活动都须要前端人员介入,甚至替换一个简单的图标和简单的布局,都须要排期等待,吃掉了50%的前端资源。
  2. 市面上可以使用的一些在线制做推广平台制做的页面又不能很好地结合到本身的业务流程里面。
    • 转盘抽奖,若是使用第三方平台须要在活动结束后把抽奖名单导出,而后导入本身的平台里面去作匹配,而后在筛选中奖名单,很不方便。
    • 拉新送红包,使用第三方的平台若是用户提交了拉新的手机号。须要按期去同步数据而后送红包,不能对接本身的平台作到实时。

针对这些问题团队迫切须要一个平台来提供运营快速建立活动,开发也能在这平台作一些功能扩展。最好能知足已下几个要求:html

  1. 丰富的组件提供运营能自主建立页面。
  2. 每一个作好的页面均可以设置为模板页面,提供运营下次快速经过模板建立页面简单修改而后发布。
  3. 提供经常使用动画然运营能建立炫酷效果的活动。
  4. 提供每一个活动完整的数据分析方便运营查看效果,常规的pv,uv,以及自定义页面的元素点击打点和统计功能。
  5. 提供灵活的页面管理,方便运营按组按项目维度给其余同事分配权限统一管理。
  6. 开发人员能够为组件植入脚本灵活扩展该活动的功能,方便运营使用。
  7. 提供统一的组件开发规范,方便开发新的业务组件为运营提供更友好的使用方式。

针对这些要求咱们作了码良平台,码良是一个在线H5编辑器,用于快速制做H5页面。用户无需掌握复杂的编程技术,经过简单拖拽、少许配置便可制做精美的页面,可用于营销场景下的页面制做。同时,也为开发者提供了完备的编程接入能力,经过脚本和组件的形式得到强大的组件行为和交互控制能力。 前端

核心设计

下面会分享下咱们的核心设计,此次主要重点说明下面几方面内容vue

  1. 咱们会介绍总体的架构来了解通常的编辑产生页面的基本思路,基于数据编程。
  2. 咱们会介绍核心的组件如何设计,确保能够自由扩展组件能力
  3. 咱们会介绍如何设计编辑器达到可自定义属性的控制面板 <span style="color:red"> 备注(因为总体项目实现使用的VUE,因此后面有部分介绍具体技术实现的时候会以VUE的使用角度说明。用其余框架的自行脑补)</span>

总体架构

  1. 总体架构 总体架构相对简单,核心就是定义一套标准的数据规范,提供一个编辑器去编辑这个数据,同时提供一个解析器去解析该数据,而后渲染出页面,流程以下。
  2. 数据结构 经过上面的图看到每一个页面是由不少节点组成(node),每一个节点能够嵌套子节点。而每一个节点包括的基本信息以下,<span style="color:red">备注文章后续提到的 nodeInfo 都是该节点对应的以下数据</span>
{
  "id": "truck/button1l",
  "type": "truck/button",
  "label": "按钮1l",
  "version": "0.1.4",
  "visible": true,
  "style": {
      "position": "absolute",
      "width": "100px",
      "height": "40px"
  },
  "animate": [],
  "props": {
      "text": "输入文字",
      "type": "danger",
      "click": []
  },
  "path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/button/0.1.4/index.js",
  "script": "",
  "events": []
}

每一个组件比较核心的元素由以下几部分组成java

  1. id 元素的惟一编号。方便代码获取和操做
  2. type 组件的类型。会根据不一样的类型加载不一样的脚本资源,而后运行加载完的脚本会建立一个VUE Component,而后会把这个Component 挂载到VUE全局,因为每一个组件节点都是一个 动态的 Component 组件。这时候只须要修改动态组件的 :is 数据进行内容替换就行了。
  3. label 组件别名。方便运营理解使用
  4. version 组件版本。 每一个组件都是有本身的版本的。
  5. style 组件样式
  6. props 组件参数。每一个组件都是有一些初始化参数的,这些参数都是营销人员在编辑器里面填写的。这些参数就存放在这里面,在扩展编辑器属性能力里面会详细说明
  7. script 扩展脚本。每一个组件能够插入一些脚本代码扩展组件的功能。这些脚本建立的对象会 mixin 到该组件对象里面,在组件设计里面会详细介绍
  8. event 组件绑定事件。 每一个组件能够绑定常见dom事件。
  9. child 孩子节点。
  10. path 脚本路径。 经过该路径加载脚本建立组件对象。

上图的页面包括一个图片,图片下面两个文字,图片兄弟节点有个按钮元素。对应页面的详细数据结构以下,能够感觉下完整结构。node

{
  "id": "node",
  "type": "node",
  "visible": true,
  "style": {
  },
  "props": {},
  "child": [
      {
          "id": "truck/image15j",
          "type": "truck/image",
          "label": "图片15j",
          "version": "0.1.4",
          "visible": true,
          "style": {
              "position": "absolute",
              "width": "320px"
          },
          "animate": [],
          "props": {
              "url": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/ymm-maliang/access/ymm_1533366999689.png",
              "click": []
          },
          "path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/image/0.1.4/index.js",
          "script": "",
          "events": [],
          "child": [
              {
                  "id": "truck/text3l",
                  "type": "truck/text",
                  "label": "文本3l",
                  "version": "0.1.4",
                  "visible": true,
                  "style": {
                      "position": "absolute"
                  },
                  "animate": [],
                  "props": {
                      "text": "文字内容1",
                      "click": []
                  },
                  "path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/text/0.1.4/index.js",
                  "script": "",
                  "events": []
              },
              {
                  "id": "truck/text3l5g",
                  "type": "truck/text",
                  "label": "文本3l",
                  "version": "0.1.4",
                  "visible": true,
                  "style": {
                      "position": "absolute",
                      "width": "114px"
                  },
                  "animate": [],
                  "props": {
                      "text": "文字内容2",
                      "click": []
                  },
                  "path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/text/0.1.4/index.js",
                  "script": "",
                  "events": []
              }
          ]
      },
      {
          "id": "truck/button1l",
          "type": "truck/button",
          "label": "按钮1l",
          "version": "0.1.4",
          "visible": true,
          "style": {
          },
          "animate": [],
          "props": {
              "text": "输入文字",
              "type": "danger",
              "click": []
          },
          "path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/button/0.1.4/index.js",
          "script": "",
          "events": []
      }
  ],
  "script": [],
  "animate": [],
  "version": "0.1.0",
  "events": []
}

一句话小结:页面是由不少节点递归生成,每一个节点包含布局,事件,脚本,参数,版本等信息,而后编辑器编辑这些信息,解析器解析这些信息。编程

组件设计

一个页面都是由一个个递归嵌套的组件组成,组件是整个项目的最核心的一部分,为了让组件具备扩展能力,咱们对组件的功能使用了 mixin 方式,经过基础组件逻辑+自定义脚本的形式来生成组件。下面介绍下总体<span style="color:red">组件结构</span>和<span style="color:red">初始化流程</span>,方便理解咱们是如何实现的。json

  • 上图左部分能够看到整个页面都是由一个一个node节点组成,他们是一个树状结构,每一个node节点下面包含着一个组件对象作功能展现,下面是node节点的dom结构,能够看到每一个节点都是递归节点,每一个节点内部都包含一个动态组件,每一个动态组件的经过nodeinfo.id为key的组件进行渲染。
<div class="node" v-show="visible" :style="nodeInfo.style">
    <component :is="nodeInfo.id" v-bind="nodeInfo.props" :ref="nodeInfo.id" :style="componentStyle"></component>
    <node v-if="nodeInfo.child" :info="item" v-for="item in nodeInfo.child " :key="item.id"></node>
  </div>
  • 上图右部分能够看到渲染流程。为了达到组件的高扩展性,每一个组件的功能包含两个主要部分
    1. 组件代码 ,每一个组件都是有特定参数和特定功能的脚本实现,好比 图片,富文本,分享,九宫格等组件,组件代码经过对于的type 和 path 参数去加载对于的脚本获取对象。
    2. 组件经过编辑器添加的脚本 , 编辑器能够为每一个组件动态添加脚原本加强对组件的操做能力。以下操做,能够看到一个组件能够添加多个脚本。每一个脚本其实就是一个的vue组件,终这里面的代码会建立对象 mixin 到最终的vue组件里面,因此你能够为组件扩展各类功能进行支持你的特殊业务。

一个节点的逻辑功能=组件逻辑+脚本1+脚本2+脚本3... 每一个组件在根据本身的类型加载对应js脚本后,会对该组件 nodeInfo.script 里面的 逻辑进行mixin. 而后建立一个最终的组件注册到Vue.component 里面方便后续使用,核心代码以下数组

// 经过加载到的组件脚本得到的全局对象建立vue对象 window['image_1.0.3'] load组件脚本运行后会生成的对象
var component = Vue.extend( window['image_1.0.3']) 
// 遍历全部加入的脚本混合组件对象中
nodeInfo.script.forEach((value)=&gt;{
    component =component.extent(value)
})
// 以节点id为key,注册最终组件对象
Vue.component(nodeInfo.id,component)
// 修改该节点的动态组件 :is 参数为 该节点id
// done

一句话小结:经过不断的mixin新的自定义脚本进来扩展组件能力

组件属性编辑设计

属性编辑主要目的是开发组件的人会暴露一些可配置的参数给运营人员在编辑器里面填写和修改。 好比选择一个组件后再右侧属性面板能够对这个组件进行一些属性设置. 为了便于维护和扩展,咱们以为一个组件的可配置数据包括简单数据,复杂逻辑数据,对应可编辑属性的部分也分为两部分

  1. 编辑器提供基础属性编辑
  2. 编辑器能提供扩展编辑编辑能力,主要针对运营方便操做,特征性的开发组件属性的编辑功能,提供对运营友好的操做体验

下面针对这两块比较核心的内容说明下咱们如何作的。

编辑器基础属性编辑能力

对于一个组件的开发者来讲,一是定义该组件那些参数须要暴露到编辑器让运营操做,二是定义该属性对应的值经过什么控件操做。 上文在总体架构数据结构中提到了每一个node节点都有一个 props 属性,该属性就是存放着该组件可配置的参数所配置的最终值,在初始化组件的时候会把这个 props的数据传入组件进行初始化。而定义一个组件能接受那些参数则是在每一个Vue组件的props 属性上定义, 而编辑器的做用就是经过编辑器去获取到每一个对象定义的props,而后根据每一个参数的类型提供不一样的编辑控件,好比 boolean 咱们会提供 切换按钮,image 咱们会提供选择图片控件等等。扩展脚本一样能够扩展组件的可编辑属性,下面是一个扩展脚本的例子。主要说明支持的那些类型,可定义的格式。总体流程以下。

下面咱们先看一下每一个组件可定义的props 例子。

/**
 * 
 * @param type: 字段类型,支持原生类型以及【码良输入类型】
 * 
 * 码良输入类型: 
 * input    单行输入框
 * text     多行输入框
 * enum     列表单选    需提供选项字段defaultList, 支持数组、map结构
 * image    图片选择
 * audio    音频选择
 * video    视频选择
 * richtext 富文本 
 * number   数字
 * function 方法设置
 * data     json数据
 * date     时间选择
 * checkbox 多选框      同enum 不提供defaultList字段时,输入值为布尔类型
 * radio    单选框      同enum
 * 
*/

return {
  props: {
    // 原生类型
    foo: {
      type: String
    },
    // 图片输入
    fooImage: {
      type: String,
      editer: {
        type: 'image'
      }
    },
    // 日期
    fooDate: {
      editer: {
        type: 'date'
      }
    },
    // checkbox 多选
    fooCheckbox: {
      type: Array, // 此项必须为Array
      default: () =&gt; { // 且需提供初始值
        return [] // ['day', 'hour', 'min', 'sec']
      },
      editer: {
        label: '显示精度',
        type: 'checkbox',
        defaultList: [ // array 形式的选项
          'day',
          'hour',
          'min',
          'sec',
        ]
      }
    },
    // checkbox 布尔
    fooCheckboxBool: {
      type: Boolean, // 此项必须为Boolean
      editer: {
        type: 'checkbox'
      }
    },
    // enum 含选项
    fooEnum: {
      default: 'value1',
      type: String,
      editer: {
        label: '我是字段名', // 将字段名显示为可读性更强的文本,不提供此项时,显示字段名
        desc: '我是帮助文本', // 为字段提供提示信息,帮助理解字段的意义
        type: 'enum',
        defaultList: { // map结构的选项 key为值,value为显示文本
          'value1': '条件1',
          'value2': '条件2',
          'value3': '条件3',
        }
      }
    },
    // 条件属性
    ifFoo1: {
      type: [Number],
      default: 0,
      editer: {
        work: function () {
          return this.fooEnum == 'value1' // 只有当 `fooEnum` 字段取值为 'value1' 时才显示此项
        },
        label: '条件属性1',
        type: 'number',
      }
    },
    ifFoo2: {
      type: [Date, String],
      default: null,
      editer: {
        work: function () {
          return this.fooEnum == 'value2' // 只有当 `fooEnum` 字段取值为 'value2' 时才显示此项
        },
        label: '条件属性2',
        type: 'date',
      }
    },
  },
  mounted: function () {
    console.log('hello ' + this.foo)
    console.log('hello ' + this.fooImage)
    // ...
  }
}

上面脚本扩展的组件对应的增长的可配置的属性以下图。

这里面的的主要设计在于每一个props属性里面添加了一个 editer字段进行该字段在编辑器环境下提供什么组件对该属性进行编辑。editer的字段主要包括以下。

{
        
    label: '我是字段名', // 将字段名显示为可读性更强的文本,不提供此项时,显示字段名
    desc: '我是帮助文本', // 为字段提供提示信息,帮助理解字段的意义
    type: 'enum',
    ignore: true,       // 不在编辑器显示
    work:function(){
        // 若是知足什么条件才会显示
    },
    defaultList: { // map结构的选项 key为值,value为显示文本
     'value1': '条件1',
     'value2': '条件2',
     'value3': '条件3',
   }
 }
  1. label 在编辑器显示的名称
  2. desc 该字段在编辑器详细描述
  3. type 编辑该属性的组件类型
  4. ignore 负略在编辑器显示,通常在该属性提供了高级编辑模式须要隐藏掉默认的模式。
  5. work 一个方法,该方法返回true 会在编辑器显示该属性,一遍用于联动隐藏和显示一些编辑属性
  6. defaultList 一些默认数据,通常提供单选,下拉等默承认选择的值。

一句话小结:编辑器经过获取每一个组件的props,遍历每个属性,按类型提供不一样的操做控件,编辑生成最终的数据放到 nodeInfo.props上。

扩展编辑属性能力

不少时候一个组件可配置的属性按咱们的规划来讲就下面几种类型。

/**
 * 
 * @param type: 字段类型,支持原生类型以及【码良输入类型】
 * 
 * 码良输入类型: 
 * input    单行输入框
 * text     多行输入框
 * enum     列表单选    需提供选项字段defaultList, 支持数组、map结构
 * image    图片选择
 * audio    音频选择
 * video    视频选择
 * richtext 富文本 
 * number   数字
 * function 方法设置
 * data     json数据
 * date     时间选择
 * checkbox 多选框      同enum 不提供defaultList字段时,输入值为布尔类型
 * radio    单选框      同enum
 * 
*/

若是按每一个类型提供一个基本的编辑组件就能完成90%的需求,不过在随着组件的复杂度增长,每一个组件可配置的属性变得千奇百怪,各类需求均可能。好比一个简单的多选,原来的可选项只能写死,如今须要本身请求接口获取。但这些逻辑咱们不能作到统一的编辑器里面,也不能作到组件里面,因此只能在作组件的时候提供一种机制让开发组件的同窗开发组件的同时,还能对这个组件开发一个自定义的编辑器,并能整合到咱们的属性编辑面板中。 总体架构以下,最终效果能够参考上图的自定义面板部分

一个组件打包完通常会有两个必要的脚本,一个是组件对应的js。一个是该组件对应编辑器的脚本js。 整个平台对编辑器的功能扩展都是相通的,经过加载脚本,建立对象,注册到Vue,而后经过动态组件渲染。对编辑器属性的扩展也是同样。加载对应组件的编辑器脚本,而后按相同的方法进行植入。这里就不在细讲。这里简单分享下咱们对一个组件的开发最终的结果。以下图

发过程当中的界面

布后在码良编辑器里面的样子

组件动画展现

运营活动对一些简单的动画提供支持,方便作一些入场和出场的动画,提高活动的交互感,咱们使用了 animate.css 提供的一套css动画。下面提供简单的展现

合成组件思考

合成组件就是选择已有的节点保存为一个通用的组件,方便下次直接使用

使用组合组件

导出组合组件

模板页面

页面模板的目的和组合组件相似,都是提供已经作好的内容,运营快速选择使用达到快速上线活动的目的,下面是简单演示

总结

为了提供一套对运营友好,而且高扩展的h5活动制做平台咱们作了这个码良平台。如今码良的平台如今支撑着运满满天天新增5-10个的新活动页面的需求,已有活动模板的活动95% 能够营销人员经过模板建立,作些样式或图片的修改,而后发布到线上,整个过程就几分钟。活动的模板和组件模板也在不断沉淀,相信沉淀一段时间后随着模板愈来愈全,对营销活动的快速制做和可选择性都会更强。

交流群

钉钉 微信
若是上面微信二维码过时。点击我查看最新二维码 添加的时候备注上 码良

开源代码

官网: https://godspen.ymm56.com/

使用手册: https://godspen.ymm56.com/doc/cookbook/introduce.html

在线体验: https://godspen.ymm56.com/admin/#/home

私有部署: https://godspen.ymm56.com/doc/cookbook/install.html

做者和贡献者

王坤明 运满满上海前端Leader 码良核心架构设计
魏明圆 码良项目主要开发
潘阿茹 核心模板贡献者
柳刚 码良后台服务代码主要贡献者
彭辉 表单,步骤条等组件贡献者
相关文章
相关标签/搜索