如何设计实现H5营销页面搭建系统

背景

近几年,low codeno codepro code等愈来愈多的出如今咱们的视野中。抱着不被卷的心态 🐶,我决定来深刻探索一下。html

我所在的是营销部门。天天/月都承载着大量的营销活动,本文也是我在探索可视化搭建过程当中的一些心得体会前端

其实这些名词都与搭建相关。其中一个应用最广的场景就是营销。咱们知道不管是淘宝、京东这些电商巨头,亦或是携程、去哪儿这些OTA,天天 APP 上都承接着无数的活动页面。vue

大体梳理一下营销活动的一些特色:react

  • 页面相似: 页面布局和业务逻辑较固定
  • 需求高频: 每周甚至天天有多个这种需求
  • 迭代快速: 开发时间短, 上线时间紧
  • 开发耗时: 开发任务重复, 消耗各方的沟通时间和人力

不一样于常规的业务开发,营销活动每每受影响的因素不少:节假日大促、政策规则等,因此每每多是今天上午说的活动,明天就要上这种。若是单靠前端同窗去维护,那怕不是要加无数的班(好比以前的我 😭)npm

每次来一个新活动,都靠前端同窗去画页面,显然这种效率是极低的。若是排期宽裕点还行,若是遇到618双11怕不是要逼疯咱们。。api

楼层搭建

鉴于这种场景,内部也进行了不少的讨论。得出的一致结论就是:开发同窗提供营销搭建后台,页面作成可配置化,配置的工做交给产品/运营同窗。这样,基于楼层搭建营销页面的方案就应运而生了。数组

其实楼层搭建在营销页面的搭建中是一种比较常见的方式。 如上图是京东的一个活动页面,页面主要由三部分组成:头图楼层、优惠卷楼层、热销楼层。由于就像生活中的盖楼同样,因此在早期的营销搭建中,就有了楼层的概念。每一个楼层其实就对应了一个具体的组件。 而后在具体楼层的编辑内容区域就能够去上传对应的数据了。缓存

但这种方式有一个很大的缺点就是:不够直观。随着业务的快速迭代,也陆续获得了一些反馈。最终发现运营同窗真正须要的是那种能够直接拖拽生成页面的,也就是可视化搭建markdown

可视化搭建

楼层搭建的基础上进一步改造为可视化搭建,复杂度提高了不少。单纯的去看页面的不一样呈现,可能仅仅就是加了一个拖拽的操做。但真正准备去落地的时候,发现其中的细节特别多,也包含了不少的设计理念在里面。数据结构

咱们先来看一下原型图,而后仔细分析一下须要作的事情: 市面上大部分营销可视化搭建系统基本都是相似上图这样的页面呈现。左侧对应组件区域,中间是画布区域,右侧是属性区域。

大体操做流程就是拖动左侧的组件到中间的画布,选中组件,右侧属性面板就会展现与该组件关联的属性。编辑右侧属性,画布中对应的组件样式就会同步更新。页面拼接完成,可经过相似预览的操做进行页面预览。预览无误,便可经过发布按钮进行活动的发布。

流程梳理完,咱们来看下项目的基础架构:

这里我基于原型对项目设计进行了功能的铺平,其实仍是围绕组件画布属性面板这三块。

到这里,咱们思考几个问题:

  • 画布区域如何渲染已添加到画布中的组件(组件库组件会不少,画布中可能只需添加几个组件,考虑如何作动态渲染)?
  • 组件从左侧拖入画布区域,选中组件,就可知道该组件关联的属性。组件 Schema 如何设计?
  • 画布区域和预览时组件的渲染是否可共用一套渲染逻辑?
  • 组件的数据如何去维护(考虑添加组件、删除组件、组件渲染/预览等场景)
  • 组件库如何维护(考虑新增组件知足业务须要的场景)

首先来看第一条,简单概括就是动态加载组件

动态加载组件

若是你常用vue,那我想你对vue中的动态组件确定不陌生:

<!-- 当 currentView 改变时组件就改变 -->
<component :is="currentView"></component>
复制代码

市面上的大部分编辑器也都是利用了这个特性,大体实现思路就是:

  • 用一个数组componentData维护编辑器中的数据
  • 将组件拖动到画布中时,将此组件的数据pushcomponentData
  • 编辑器遍历(v-for)组件数据componentData,将组件依次渲染到画布中

因为我在的团队包括我本身一直都在使用react,这里着重来提下react组件动态加载的实现方式,框架使用的是umi

我在实现这部分功能时,在umiapi中找到了dynamic 封装一个异步组件:

const DynamicComponent = (type, componentsType) => {
  return dynamic({
    loader: async function () {
      const { default: Component } = await import(
        `@/libs/${componentsType}/${type}`
      );
      return (props) => {
        return <Component {...props} />;
      };
    },
  });
};
复制代码

而后在调用的时候,将组件数组传入便可:

const Editor = memo((props) => {
  const {
    componentData,
  } = props;
  return (
    <div> {componentData.map((value) => ( <div key={value.id} > <DynamicComponent {...value} /> </div> ))} </div>
  );
});
复制代码

解决了第一个问题,咱们来看第二个,也就是:组件 Schema该如何设计

组件 Schema 设计

这里涉及到组件、画布和属性区域三块的联动。主要包含组件强相关的表单属性以及初始值。

因为涉及到组件属性的字段限制及校验,为了规范和避免出错,建议项目使用 ts

这里以一个TabList组件为例,展现一下它的Schema结构:

const TabList = {
  formData: [
    {
      key: 'tabs',
      name: 'Tab名称',
      type: 'TitleList',
    },
    {
      key: 'layout',
      name: '布局方式',
      type: 'Select',
      options: [
        {
          key: 'single',
          text: '单列',
        },
        {
          key: 'double',
          text: '双列',
        },
      ],
    },
    {
      key: 'activeColor',
      name: '激活颜色',
      type: 'Color',
    },
    {
      key: 'color',
      name: '文字颜色',
      type: 'Color',
    },
    {
      key: 'fontSize',
      name: '文字大小',
      type: 'Number',
    },
  ],
  initialData: {
    tabs: [
      {
        id: uuid(6),
        title: '华北',
        list: [
          {
            icon:
              '',
            goCity: '烟台',
            backCity: '北京',
            goDate: '08-18',
            goWeek: '周三',
            airline: '中国联合航空',
            price: 357,
            disCount: '4',
          },
        ],
      },
    ],
    layout: 'single',
    color: 'rgba(153,153,153,1)',
    activeColor: 'rgba(0,102,204,1)',
    fontSize: 16,
  },
};
复制代码

在组件初始化时就约定好其对应的结构,当将组件拖入画布区域后,咱们能够拿到当前选中的组件数据,而后右侧的属性面板就能够渲染出对应的可编辑表单项。来看下右侧表单区域的代码:

const FormEditor = (props) => {
  const { formData, defaultValue } = props;
  console.log('FormEditor props', props);
  const [form] = Form.useForm();

  const handleFormChange = () => {
    console.log('表单更新',form.getFieldsValue());
  };

  return (
    <Form form={form} initialValues={defaultValue} onValuesChange={handleFormChange} > {formData.map((item, i) => { return ( <React.Fragment key={i}> {item.type === 'Number' && ( <Form.Item label={item.name} name={item.key}> <InputNumber max={item.range && item.range[1]} /> </Form.Item> )} {item.type === 'Text' && ( <Form.Item label={item.name} name={item.key}> <Input /> </Form.Item> )} {item.type === 'TitleList' && ( <Form.Item label={item.name} name={item.key}> <TitleList /> </Form.Item> )} {item.type === 'Select' && ( <Form.Item label={item.name} name={item.key}> <Select placeholder="请选择"> {item.options.map((v: any, i: number) => { return ( <Option value={v.key} key={i}> {v.text} </Option> ); })} </Select> </Form.Item> )} </React.Fragment> ); })} </Form>
  );
};
复制代码

表单区域具体表单项发生改变后就会触发onValuesChange,也就是ant design表单的字段值更新时触发回调事件。这时数据就会更新到store中。而画布的数据源就是store中的componentData进而页面会实时更新。来看下总体的数据流转图:

至此,第二个问题也就解决了。

接着看第三个问题:画布区域和预览时组件的渲染是否可共用一套渲染逻辑?

组件共享

咱们能够把预览组件理解为画布区的静态版本或者快照版本。从页面呈现上来看并无太大的差别,那么从代码设计上,这两部分固然就能够共享一个组件。咱们把这个共享组件叫作RenderComponent.tsx,数据源为store中的componentData,而后结合DynamicComponent组件,就获得了以下代码:

const RenderComponent = memo((props) => {
  const {
    componentData,
  } = props;
  return (
    <> {componentData.map((value) => ( <div key={value.id} > <DynamicComponent {...value} /> </div> ))} </>
  );
});
复制代码

数据存储/分发

至于第四个问题:组件的数据如何去维护(考虑添加组件、删除组件、组件渲染/预览等场景),其实在上面回答第二个问题的时候,已经提到了。全局有维护一个store

state:{
  // 全部添加到画布中的组件数据
  componentData:[],
  // 当前编辑的组件数据
  curComponent: {}
}

reducers:{
  // 添加组件到componentData
  addComponentData(){},
  // 编辑组件,更新componentData及curComponent
  editComponentData(){},
  // 删除组件
  delComponentData(){}
}
复制代码

对于可视化编辑器这种大型前端项目,须有一个全局状态管理机制去作数据的存储和分发。这样对于数据状态的共享和同步也是颇有帮助的。

组件开发/维护

来看上面提到的最后一个问题:组件库如何维护(考虑新增组件知足业务须要的场景)

这种目前有两种通用的作法:

  • 直接放在项目中
  • 抽成 npm 包,造成独立的第三方组件库

若是是项目初期,我感受第一种作法也不是不能够,方便调试。但长远来看,营销场景下沉淀出来的组件绝对不会少,抽成第三方 npm 包才是明智的选择,同时要配合一个相似组件管理后台的管理系统,对组件作统一的管理。

回到组件自己而言,必须有严格的开发规范。每一个组件原则上只是呈现上的不一样,对于约定俗成地组件研发规范则必须遵照。至于如何去限制,能够经过文档(弱)或者 cli(强)去作。

模板

除了上面的几个问题,还有一个点没提到:模板。咱们知道营销活动有一个很典型的特色:页面相似。若是运营/产品同窗从零去生成一个页面也是挺耗费时间的,并且大部分活动都是归属于某一个大类下面的,咱们能够把这些类似的活动抽成模板。基于模板建立就会省时省力不少。鉴于这部份内容还在开发迁移中,暂时就不展开细说了。

到这里,我感受已经把可视化编辑器实现上最为复杂的几部分以问题的形式一一解答了。其实不管是组件动态加载仍是组件schema的设计数据结构的设计组件库的维护等,每一个团队均可以制定一套适合本身的规范,没有绝对的对错之分。

其实在这个编辑器的实现过程当中,有不少不容咱们忽略的底层实现细节。包括:

  • 拖拽
  • 组件图层层级
  • 放大/缩小
  • 撤销/重作
  • 吸附
  • 绑定事件/动画

这些细节我就不一一展开说了,推荐一篇文章:可视化拖拽组件库一些技术要点原理分析。文章对于上面提到的技术要点都有很详细的说明。

low code/no code/pro code

上面说了这么多,下面让咱们回到文章最开始提到的low code/no code/pro code。我会结合咱们的可视化编辑器来阐述一下这三者。

首先来看下运营/开发同窗使用编辑器建立活动的大体流程:

no code

首先来简单说明一下,什么是no code:从字面上来看就是无代码,也就是不写代码。

从上面的流程图中,能够看到运营/产品同窗经过可视化编辑器,不用写一行代码,就能够搭建出功能齐全的活动页面。这种对应的就是no code

low code

low code的定义则是低代码、少写代码。

在上面的流程图中,更多体如今前端同窗开发组件库。须要写部分代码,总体经过拖拽的方式生成的方式。对应的就是low code

pro code

pro code的定义是纯代码,也就是不经过任何可视化工具,全靠开发手写的代码形式。在low codeno code出现以前,这种方式是最为广泛的研发方式。

在上面的流程图中,这部分并无体现。可是在实际的业务开发中,这种场景倒是常常存在的。可能当前的一个营销活动,交互复杂、链路长,那经过本文这种可视化编辑器是很难去定制的。只能经过开发去手动写代码的方式去知足业务需求。

可视化编辑器更多的是去知足规则相似的页面开发,首要职责是去减轻重复业务的开发

展望

至此,一个营销系统的搭建探索演进流程我就大体梳理完毕了。

但,这只是一个开始。本文更多的是侧重于前端侧的探索,也仅仅是向可视化编辑器迈出了第一步,只是一个更倾向于纯前端的项目,不少逻辑都尚未考虑。这里列一下后面要作的吧:

  • 模板市场
  • 数据中心
  • 埋点
  • 组件调试/预览
  • 缓存
  • 开放 api 能力
  • CDN
  • 跨端
  • ...

❤️ 爱心三连

1.若是以为这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~

2.关注公众号前端森林,按期为你推送新鲜干货好文。

3.特殊阶段,带好口罩,作好我的防御。