作者:徐桑 & 刘哲
你进来后的内心OS:菜单还能搞出什么花样? 行,接着往下看。
背景
为了方便各位了解电子菜单是什么,所以先放一下关于古茗菜单的”演变过程”,如图可见可以看到古茗菜单的演变是从纸质版到现在的电子版,从投放图片到现在投放h5链接,中间的过程经历了什么以及我们为什么要这么做,下面带着各位的疑问以及不解我们开始发车啦~
贴图:
注: 图片版电子菜单和h5版本电子菜单贴出不同的菜单是为了方便区分是两种不同的类型,但实际上图片版电子菜单也是可以通过建站时拖拽物料进行一比一还原~
业务现状
在h5版本的电子菜单上线之前,我们的电子屏一直投放的是一张图片,但是古茗目前有8k家门店,会根据不同的区域和不同的品牌店(古茗茶饮/goottt)下发不相同的菜单,于是一些问题就被暴露出来了,例如:
- 每次茶饮有更新时,品牌设计部的小伙伴都需要去设计100-200张菜单设计图,并且无法保证设计师出图的菜单完全无误。
- 图片无法及时同步商品上下架信息,顾客通过菜单进行点单后门店告知商品已售罄。
- 无法同步展示促销活动,影响履约效率,降低顾客点单体验
通过上述的问题我们决定搭建一套可以帮助品牌设计师快速设计的一套交付体系。
收集需求
既然要开始做了则先去收集一些相关业务方的诉求,诉求如下:
- 部门A:需要展示完整菜单;需要置顶新品展示,所以每次都需要根据地区和上新更换菜单;
- 部门B:需要展示精简菜单;配合新店做活动,品牌也需要根据需求来更换;
- 部门C:需要展示促销活动、展示划线价,用作小程序引流和价格策略的落地;
- 部门D:需要菜单上可以实时同步展示商品的销售状态和活动标签;
总结:菜单版式会不定期调整,固定版式无法满足需求。
基于以上诉求,则产生了自定义建站、定制化通用物料的等等需求,于是乎就产生了下面的内容:
- 后台建站设计方案。
- 前台电子菜单渲染方案。
建站设计方案
功能预览
首先设计师是习惯了sketch或者Photoshop这些作图软件的交互方式的,通过拖拽组合的方式更符合设计师操作习惯,总不能让他们通过命令行工具来生成图吧,所以我们做了这样一个工具,并且为了使得业务使用起来更加自由所以采用了自由画布。
实现原理
技术选型
因为需要对节点实现拖拽,放大缩小,自由排布,配置等操作和图形学相关,首先想到Canvas或者SVG,对比如下
最终我们认为,在强交互并且节点不会特别多(100+)的情况下,SVG是更优选,所以技术选型为Antv-X6,原因如下:
- 事件处理和JavaScript的事件差不多。而Canvas的事件方式有以下缺点
1. 只能在Canvas整体上绑定事件,通过图形和点击处的碰撞判断是否点到了相应区域,万一图形是多边形,还得去恶补数学原理。
2. 事件会冲突,例如drag和click,dbclick和click需要自己区分。
3. 没有事件模型,无冒泡和捕获。
- 相比较来说,SVG自定义节点方便,并且SVG拥有foreignObject的原因,可以通过HTML的方式来自定义模块,来把一个React组件当做自定义组件。应该不会有人喜欢用类似Canvas那样的命令式代码去实现一个组件吧。
如何自定义节点
在上面我们提到了SVG拥有foreignObject标签,通过这个标签我们可以写任何的html语法,例如:
1 | xml复制代码<svg xmlns="http://www.w3.org/2000/svg"> |
如果你不想自己写一堆SVG标签来描述图形的话,现在有另外的选择了,每一次拖入到画布的内容,就相当于插入了一个foreignObject标签,所以标签里的内容可以是React组件,当然如果你喜欢也可以是Vue组件,HTML组件,Angular组件,只要是html结构。
我们用的是React函数式组件的,好处就是无状态,数据驱动视图,View = Fn(props) ,他受传入的props的影响来展示不同的视图,比如受到右侧的配置栏的影响。并且所见即所得,现在拖拽生成是什么样子,后续在电子屏上展示也是一模一样。
数据联动带动视图联动
显然,我们目前后台开发用的都是Antd,通过每一种节点模块配置一个表单组件的方法,在最外层监听表单变化并且同步更改节点对应的Schema。在上面我们也提到了View = Fn(props) ,并且Svg内部渲染的是可以是任何类型的组件或Html,所以我们只需要将表单所更改的数据同步到Props中就可以实现数据联动带动视图联动。
onValuesChange只能监听用户操作的表单变动,代码的主动操作表单变化无法监听(因为会引起重复渲染),shouldUpdate是一个比较前后值变化,返回布尔值决定是否更新的api,所以能监听到所有变化(业务开发千万别在这里做一些多余操作)
对于画布实例、操作的历史记录等等都会放在全局数据来进行管理,并且对于全局数据的管理没有引用多余的包,仅仅用了useContext和useReducer去做状态管理。
「如无必要,勿增实体」,对一个简单需求的过度抽象封装,或者引入更大的解决方案,可能只会徒增成本,并不会带来效率的提升
描述页面的信息协议
通过上述的操作我们已经可以实现建站操作,背后所1比1渲染出的节点,实际是赋予了“协议”中所定义的各种属性值,包含了节点ID,位置信息,所传入的props等等,导出例子如下:
1 | css复制代码{ |
通过上述导出的数据结构我们可以看到其包含了位置、长宽、物料类型等等,这些数据结构我们会在后面在后续菜单渲染时使用。
其他&展望
在web图形化编辑器,已经有很多成熟的产品了,例如低代码编辑器,在线图形编辑,视频剪辑,在如何突破浏览器的性能瓶颈,figma已经是图形编辑器的性能天花板了。
- 在语言层面上突破瓶颈,js是解释型语言,在执行的时候需要转化一次,对于CPU密集型的操作,比如图片视频剪辑和3D游戏等有天然的瓶颈,如果用了wasm,无需解释执行,直接把二进制码变成机器码。并且wasm和js可以相互通信共存,把js的灵活性和c++的高性能结合起来。做法是用C++写代码,然后转成js代码在浏览器上执行,在2017年之前,他们用asm.js来转代码,2017年之后浏览器实装WebAssembly了,也就是wasm,直接顺水推舟完美切换,并且性能暴涨3倍,12s加载的画板文件只要4s就能加载出来。
- 在硬件层面上突破瓶颈,用webGl来绘制图形,可以调用GPU硬件加速,让画面更加流畅
电子菜单渲染
前置问题
- 如何把建站的模版展示在电子菜单屏上?
- 如何针对不同的门店做内容隔离?
- 如何保证菜单内容的稳定展示?
如何把建站的模版展示在电子屏
我们电子菜单是在一个第三方的Android应用中渲染的,可以依托这个宿主环境来获取一些信息比如说清除整个电子屏缓存、获取门店编码、存储内存、版本等信息的操作,在Android通过webView来去渲染对应的h5网页,并且会在初始化完毕后通过桥接来调用我们在js中定义好的入口函数,并传参一些电子屏相关的信息,我们通过这个JS函数,来开启后续一系列逻辑的执行。
知道了渲染的宿主环境后,然后我们了解一下如何把对应的模块渲染在电子屏上,首先先看一下电子菜单导出的部分数据结构(下面以列表型商品组件为例),通过下面的数据结构可以看到position属性中存在节点的x和y轴坐标,也就可以通过坐标确定出此节点在这幅画上的位置了。
拥有位置坐标后,需要确认当前在此位置上如何绘制出建站时的样子。实际上在建站和渲染时,所用到的都是同一套自定义组件,所以只需要将建站时导出的数据传入到此自定义组件就可以1比1还原出建站时所使用的物料组件,也就是上述所说的View = Fn(props) ,那么多个节点在多个位置渲染就会得到一幅画,如下图所示。
如何在门店维度进行菜单内容的隔离?
众所周知,古茗有多个品牌,例如古茗茶饮、零氧化(虽然没见过)、goottt。品牌不同,所展示的内容也应有所差异。
又例如,在古茗茶饮这个品牌下,门店经常会换活动,今天的秋天第一杯奶茶,明天的七夕节活动,大后天的xxx新品上新,这使得我们每次在建站时添加的物料组件的都不同,我们每次需要一个给门店下发一个固定形态,所以版本的概念产生了,版本再往前推,此版本主要是来描述作图或设计方案的固定格式,所以模版的概念也产生了。
将模版下发给门店前,需要将模版执行”定稿”操作,”定稿”这个动作主要是为了保证打包后产生的版本产物所有的形式、状态、内容是固定的,通过”定稿”会将当前物料组件和render层的逻辑打包为以hash值命名的产物来作为当前资源快照,对门店下发菜单时,只需要将门店与hash版本绑定,并让门店访问hash版本的资源,这样就可以保证我们即使变更了物料组件和render层的逻辑,之前已经下发的模版对应的元数据和渲染层仍然可以适配,因为所有东西都被定格在了那瞬间,保证了端上展示的菜单形态更稳定。
定稿(资源打包)完毕后,通过回调就可以将物料组件版本hash和JSON与模版绑定,如下图所示,一个模版版本对应的一个固定的JSON和一个固定的物料组件版本,这样就可以实现只需要对门店下发不同的模版,就可以实现菜单内容隔离了。
如何保证电子菜单端上稳定性
每个门店的网络情况不同,对于网络比较差的门店,需要做的是让这些门店在加载一次过后,后续的加载无论失败都可以正常展示,所以将其打造成为一个PWA应用,增加离线使用能力。
Service Worker的主要思想是在页面和网络之间增加一个拦截器,用来缓存和拦截请求,由于POST请求不可以被CacheStorage缓存的原因,所以我们针对接口请求和资源请求分别缓存在IndexDB与CacheStorage中,使得门店弱网与断网的情况下仍然可以正常展示菜单,堂食点单不被影响。
总结
随着古茗门店的增加,公司整体信息化的诉求越来越强。我们想要提高门店整体的经营效率,避免人力出现的各种问题和带来的损耗,也想提高用户的点餐体验,所以才产生了电子屏菜单这个项目。
技术是为了服务于业务的,业务也是证明技术的最好场景,不能脱离业务去搞一些不切实际的技术建设,也反对强行把自己的做的技术项目匹配到业务里,强制性对别人进行落地,美其名曰”赋能”。优秀的工程师是能从业务中发现问题,设计解决方案,最终用技术的手段解决问题的。
单纯的一项技术没有好与坏,只有适不适合。例如低代码的搭建后台在大部分场景,尤其是交互复杂的场景,并不适合前端开发者去用,反而是增加大家的开发成本。但是如果是给没有前端开发能力的人用,例如本项目中给电子菜单屏幕的设计师用,就能解决问题,带来技术本身的价值。
最后
关注公众号「Goodme前端团队」,获取更多干货实践,欢迎交流分享~
本文转载自: 掘金