干货 | 降本增效,携程市场DIY商品卡片系统的设计与实现
携程各个BU各个时期都有不同营销页面,数量众多,其中很重要的一块是产品模块,运营需求的产品卡片样式众多,各个BU展示字段差别巨大,无法利用通用样式,因此如需新增卡片或字段,传统做法是运营提需求给设计,再提需求给开发,经过需求评审,正式开发,发布测试上线等等。每次遇到大促活动或者接入新的业务方,都需要重新设计及开发商卡,而新的商卡大多只是新增一些换肤样式,或个别字段,这却需要开发人员多写一套样式代码或新增字段样式,同一个样式应用于不同的业务方也需要重新进行开发,极大地浪费了开发和设计资源。在DIY商品卡片系统开发前,由于开发成本的限制,营销页面上常用的商品卡片样式基本固定为十几种,这在用户看来缺乏新意,吸引力不足,从而在一定程度上影响了营销页面商卡的点击量。
- 样式固定、对用户吸引力低导致点击率及订单转化率低。
我们从UI和逻辑解耦,UI模板在平台配置生成的角度考虑解决问题,设计了DIY商品卡片系统。商卡系统将UI和逻辑进行解耦,UI部分可在商卡配置后台进行配置,逻辑部分由开发人员处理后引入原子UI组件进行终渲染。UI在商卡配置后台中由产品或者运营人员参照设计稿进行自由拖拽式配置,并且给每个模块绑定字段,同一套配置可以同时应用于H5和React Native。原子UI组件是颗粒度更小的一种静态组件(React, React Native),可以通过手动开发或配合“产品画布”DIY这两种方式为业务组件提供渲染模板,其他使用方也可以提供一些固定的数据(契约)直接渲染单卡片样式。开发人员只需在组件内引入原子组件的npm包,对业务逻辑进行处理后把相应字段的值传给原子组件即可终渲染真实产品信息。通过原子组件的模式,可以让所有组件都“引用”线上的公共UI组件,直接应用在各商卡的业务方,轻松实现商品卡片样式的统一,避免了每个业务方重复开发同一套样式。下图为商卡搭建的部分样式:- 商品卡片样式现已摆脱对开发人员的依赖,产品运营团队可以直接通过拖拽配置的方式,轻松定制全新的样式。
- 当同一商卡模板需要更新样式时,仅需在商卡配置系统中进行配置更新,便可实现一键跨端修改的效果,使所有使用该模板的业务方同步更新。
- 针对需要定制展示内容的业务方,我们提供了定制渲染能力,允许他们在整体样式统一的基础上,保留部分商卡内容的独特风格。
- 目前,我们已拥有400多张商卡和40多种常用模板,广泛应用于携程营销页面、特卖、星球号以及携程直播等多个场景。
- 商卡样式的更新换代速度得到了极大提升,为用户带来更加新颖的体验,从而增强了点击欲望。经过AB实验测试,我们发现商卡系统搭建的样式相较于过去固定的样式,在点击率上有了显著提升。
DIY商品卡片系统主要由2部分组成:商卡渲染和配置平台。DIY商品卡片可以通过以下三种渲染方式渲染,可以根据需求和配置难度选择合适的方式:渲染组件按照在配置平台上的布局进行商品卡片的渲染,字段之间没有联动设计,只能进行简单卡片的渲染。- 同一行的两个字段,不能根据前一个字段的宽度动态改变后一个字段的位置。如下图,张图中显示正常,第二张图中当个字段评分变化为4分时,3797点评的位置还在原处,就会留有不合理的间距。
- 卡片的高度也不能根据字段的长短和是否有缺失字段进行动态调整,如下图,缺失两个标签后下方的字段并不会自动向上填补空白,卡片高度也不会相应减小,在实际应用场景中,很多商品卡片都不是固定高度,在展示商品时更倾向于紧凑的商品卡片布局,即卡片的高度要根据商品数据进行动态调整。
2) 字段的宽高和位置通过数据进行动态计算,依旧采用布局的方式
增加了行容器组件,将同一行的字段放进同一个行容器中就可以让字段位置不再依赖固定坐标。以React版渲染组件为例,行容器是一个flex布局的<div></div>,去掉放入行容器里的字段的布局属性,通过设置justify-content即可控制同一行字段的排列方式,自动补齐位置。
获取到商品数据和渲染数据后,先不对商品卡片进行渲染。通过动态算法和canvas渲染对字段是否存在和字段长度进行校验,并根据校验结果修改渲染数据。修改完成的渲染数据可以达到卡片高度和字段位置动态适应的效果。a. 当字段所需高度增加或者减少时,检查哪些字段的位置会受到当前字段高度变化的影响,将受影响字段和需要进行的调整收集起来。b. 校验所有字段后,对收集的字段对应的渲染数据进行调整。c. 调整完成后开始根据渲染数据进行商品卡片渲染。d. 商品卡片渲染完成后检测每个字段是否正确渲染,对于计算有偏差的字段调整,根据真实渲染情况再次进行微调。对于纵向的动态布局,依赖通过商品数据和渲染数据进行计算也会带来一些问题,比如在渲染数据的调整上,调整后的商品卡片可能会和配置略有差异,有一些准确性的问题。为了达到和手工开发一样的效果,增加了垂直方向的容器,称为垂直容器。放入垂直容器的字段,会被调整为相对布局的定位方式,字段依次向下排列,不依赖定位属性。考虑到商品卡片普遍情况下都是采用比较规则的布局方式,所以使用行容器加垂直容器层层嵌套的方式可以实现去布局的效果,所有字段都自动根据前面的字段动态调整位置。接入方将数据处理为契约中的格式,传给原子UI组件进行渲染。商卡渲染时,通过场景号和版本从接口中读取两部分配置数据:
- 商卡总体配置JSON:包含有效期时间、卡片宽高、背景色、圆角等。
- 商卡内字段组件列表:包含每个字段组件的css和react native样式以及一些联动逻辑的配置。
当获取到的字段组件列表长度大于0时,我们开始循环处理列表内的组件,不同的字段类型渲染成不同的组件,若遇到容器则进行递归,直到容器中的内容全都处理完。考虑到解放开发人力,大部分布局不复杂的商卡可以由产品或者运营人员根据设计稿进行配置。为了易于非开发人员操作和理解、提高配置效率,我们在以下几个模块的设计上增加了许多考量。目前支持的字段组件有普通字段、图片、普通容器、垂直容器、横向布局容器以及内联容器,这基本可以满足设计稿的布局。普通容器主要存放需要浮动的元素。垂直容器和横向容器其实就是flex布局的两种形式。内联容器是为了解决行内元素换行问题,正常用横向布局容器是没办法实现这个效果的,内联容器内的元素我们一律转成inline元素。内联容器主要应用在如下图所示的情况:套餐名称超出一行需要换行,星级以及金钻标签紧跟其后。为简化操作,我们将商卡系统设计成拖拽配置的形式,配置人员可以从左侧可选字段组件中直接将组件拖进画布。若画布中已配置了容器,则可以直接拖进相应容器中,字段间的顺序以及位置也是可以直接进行拖动调整,至于细微的位置调整,可以通过字段的样式配置进行调整。下图所示的样式配置基本上就是简单的css属性,在浮动类型这有个特殊处理:浮动类型为浮动即position="absolute"时,距离上、右、底,左边实际上代表top、right、bottom,left;浮动类型为不浮动即position="relative"时,距离上、右、底,左边实际上代表 margin-top、margin-right、margin-bottom,margin-left,这样可以减少配置项。在行容器和垂直容器的配置中,额外还有对齐方式的配置,对应的是flex布局中justify-content和align-items,容器的子元素配置中也有压缩和弹性比例系数的配置,即flex-shrink以及flex。目前的配置项几乎可以达到还原设计稿的目的。
商品卡片的渲染schema获取是在每一个商品卡片里进行的,渲染商品列表时就会有重复调用接口的问题。通过一些方式将接口请求次数尽可能减少很重要,但因为各商品卡片之间相互独立,并不能在某一个商品卡片中知道页面中都有哪些商品卡片模版ID,我们减少请求次数的方案也就是对于同一个商品卡片模版,只请求一次。商品卡片在获取到卡片模版ID后,检查当前ID是否已经有请求到的数据,有数据则直接从window上取对应的数据进行渲染,如果没有则检查该ID是否已经被标记为已经在请求数据,对于已经在请求数据的情况,监听当前数据请求成功的消息,数据请求成功后触发请求的商品卡片会通过消息推送当前模版ID的渲染schema,并将此数据存在window上。过程如图中所示。一些商品数据字段在携程的应用场景中也是比较固定的,对于这样的一些商品数据字段,我们通过提供默认样式的方式减少配置时的工作量,配置人员只需要关心这样的字段的位置、大小以及一些颜色方面的配置,不需要再关心如前缀图标、复杂组合形式等问题。
与h5不同的是,rn的样式需要进行单独处理,大部分样式处理都在商卡配置后台中用“css-to-react-native“包进行了转换,某些特殊样式由rn版原子组件单独处理。例如:h5中的内联容器在rn中要用<Text><Text/>当外层容器,里面所有子容器也由<Text><Text/>包裹,这样才能实现元素内换行。其他还有渐变背景色用LinearGradient组件处理、背景图片转换成<Image/>标签、border拆分成borderWidth和borderColor来处理、下发的rn样式适配屏幕宽高等等。
在一些场景下,商品卡片的数据之间也是有关联关系的,我们提供了字段之间关联的一些配置,如两个字段互斥,永远只出现其中一个字段;两个字段也可以是必须同时出现的关系,当一个字段有值时另一个才出现,比如有酒店LOGO时才展示酒店名称;还有的时候字段需要根据另一个字段进行样式和展示形式的调整,比如在价格为0时商品卡片的抢购按钮变为“免费”文案的纯文字。
DIY商品卡片提供了自定义插槽,渲染时可以直接传入一个JSX函数在配置位置渲染,例如:接入方传入一个直播流播放器。这样配置上更加灵活,DIY商品卡片组件也无需引入过多第三方组件。这样的做法还带来一个好处,留有一定的空间供开发人员介入,对于一些难以配置的数据展示形式,可以指定该数据的展示使用开发人员定制开发的数据展示模块,开发和配置混合的形式极大地补充了DIY商品卡片的展示能力。
商卡有默认的点击事件,一般用于商品的跳转,若一张大卡内有多个点击区域,可以用透明蒙层配置在点击区域的上方,并绑定相应的字段。数据传入时只需要传入点击事件对象数组,商卡会根据对象的name和绑定的字段名进行匹配,将对象的函数绑定在对应的字段上。通过name进行匹配就无需在新增点击事件时在代码内手动将点击事件与字段进行绑定,这带来的好处是可以减少商卡本身的开发改动,更加灵活,只需要配置字段和传入字段一一对应即可。
对于一些重复度较高的商品卡片,将相同的部分保留作为公共部分,不同的部分抽取出来单独配置是一种更合理的方式。我们将这种模式的卡片配置称为父子模块,在配置时父模块和子模块没有区别,两种模块都可以作为一个单独的商品卡片进行渲染。在卡片上配置一个空间,定义该空间的key,在渲染商品卡片时将key对应的子模块ID和商品数据一起传入父模块,父模块在配置在渲染时将子模块渲染在预留空间里,即可实现两个商品卡片的拼接。导出数据:导出该页面上的商卡样式,导出格式为txt文件,方便进行备份和复用。导入数据:为了使配置操作更为简单,可导入已有的商卡模板或部分字段组件素材,导入后在此模板上进行增删改操作,避免从0开始搭建。画布可分为三个版本:线上版本、可编辑版本、历史版本。线上版本为真实版本,历史版本为上一次保存的线上版本。只有在可编辑版本内才可以编辑,编辑后可以保存或者上线,只有选择上线才能替换线上版本。可编辑版本的设计可以将线上与编辑隔离,以免未编辑完成的画布影响线上,同时历史版本在一定程度上也是一种备份。画布提供h5版本或app版预览,预览的版本为当前选中版本。预览的商卡数据源分为两种,一种是通用数据源,即乐高平台内针对不同业务方预先配置好的数据字段;另一种为自定义数据,配置人员可以根据具体的需求以JSON的形式在乐高平台内新增数据字段。通过预览,配置人员可以在商卡上线前看到实时效果,同时通过自定义数据也可模拟一些极端情况,如某些字段缺失后,卡片布局是否会变形等为简化配置流程,重复利用素材,商卡配置后台支持其他商卡导出的素材txt文件以及设计稿导出的元素素材txt文件导入进画布。设计稿导出的为较小颗粒度的元素,如一个名称字段、商品图片,不包含容器类大颗粒度元素的导出。设计稿直出可以省略部分基础配置,如:字号,字体颜色,图片宽高,边框及圆角等,配置人员只需专注于布局的配置即可,减少了基础样式的配置时间。其他商卡导出的素材可直接完美复制原效果,包括字段绑定的名称等,这种方式导出的素材不止是小颗粒度的元素,也可以是整个容器甚至整张卡片。除了直接导入,商卡系统有自带的组件库,可将其中的组件直接拖拽添加进画布。目前支持的组件有字段、图片、背景方块、横向布局容器、垂直布局容器、内联容器和普通容器。通过选中画布中的某个字段或容器可以配置其css属性、调整容器内子元素的顺序和对齐方式等。除了样式配置,还有字段属性配置,字段要与对应的字段名进行绑定才能正常显示,若画布中配置了某个字段组件,但数据中没有此字段名,终上线后也是不展示此字段组件的,唯有一一对应才能正常展示。配置完成后可以先通过预览查看实时效果,避免上线后出现问题。上线后接入方通过的商卡场景号调用该商卡,按照契约内容传入字段即可成功渲染。DIY商品卡片组件主要通过npm包的形式引入,按照契约传递数据即可渲染。渲染商品卡片所必需的数据,如酒店名、酒店星级、价格、主图链接等商品信息。在我们的商品卡片中,可自定义的处理逻辑主要是在点击事件中。组件支持对整个商品卡片的点击方法进行覆盖,也支持对单个字段如酒店标签进行添加点击事件处理逻辑。对于商品卡片的渲染,提供了0%~程度的自定义渲染方式。0%,就是接入方开发人员只需要传递商品数据,无需关心数据的渲染。,即是接入方开发人员可以将对应商品数据的整个渲染完全覆盖掉,DIY商品卡片组件使用render props的方式渲染接入方传递进来的渲染方法。不到又高于0%程度的渲染,则是提供给接入方传入指定商品数据的CSS以追加的形式修改原有CSS的能力。由于传统商卡开发模式无法适应当前业务的快速发展,我们通过对开发中现有问题的分析,将UI和逻辑解耦设计了商卡系统,通过对CSS模块的抽象,设计了商卡各个字段组件。商卡系统这使得我们能够以低代码平台的方式实现通用的样式跨端解决方案,从而达到降低成本和提高效率的目的。这个系统同时表明,前端工程师不仅需要关注样式和解决渲染问题,还可以通过对CSS、渲染机制、数据结构和框架的深入理解来解决更广泛的问题。