记一个电商装修页面的开发

引言
公司有一个业务是为商户开发微信商城.长期以来,首页只存在若干固定模板,有些商户要这个模块,有的要那个,众口难调,开发一个自定义装修页面也就提上议程.

调研阶段

类似这种业务,业界已经有很多例子,参考了微盟,有赞等竞品之后,决定借chao鉴xi有赞的模式.
项目经理兼业务主管结合业务选定了需要支持的组件,并制定组件规范.我开始调研技术方案.

首先梳理页面.

设置时需要能够实时预览页面效果,这里有两种方案:

  1. 为每个组件在设置端写一个预览组件;
  2. 直接把H5页面嵌入进来,设置时,通过某种方式通知H5页面重新渲染.

第一个方案实现简单,但是组件必须写两套,完全是重复性工作,而且在不同的项目,完全不可复用.效果上也很难保持一致,做不到所见即所得.长期维护之后,差异会越来越大;
第二个方案技术上存在一些难点,但如果能实现,可以直接使用最终用户所看到的组件,真正的所见即所得,由于是同一套组件,不存在效果不一致的问题.

最终选择了第二个方案,但不是直接嵌入H5首页,而是专门在H5项目中新建一个预览页面,引用与首页相同的组件.这是为了避免把一些与商城业务无关的逻辑放在首页,增加体积.同时也为了拆分关注点.

页面布局与有赞相类,左中右三列布局,左侧组件栏,中间预览页面,右侧设置栏.
设置时,从左侧拖选一个组件,拖放到预览页面,选择合适的位置放下后,即可在右侧对这个组件进行编辑.
布局
因此最终需要三个页面,PC端设置页面,H5预览页面,H5商城首页.

技术验证

H5和PC分属两个项目,两个域名,跨域通信自然只能选择postMessage方案.同时拖拽就不能是基于mouseEvent的dom拖拽—dom无法移动到iframe上,必须是浏览器的drag&drop才能支持.
为了验证这一点,我编写了两个简单的html页面,用serve将其部署在不同的web网站下.
结果发现默认情况跨域无法拖拽…
所幸H5与PC端主域名一致,尝试给两个页面设置document.domain为主域名,果然问题解决.

例如https://baidu.comhttps://h5.baidu.com,设置domain为baidu.com,两者即可跨域拖拽.

同时,为了简化两端通信,借用以前多次使用的EmitAble类,将其完整扩展.使其支持事件的多次绑定,清理,移除.然后基于这个类,编写了一个Poster类.接受到远程事件时,将payload派发给事件订阅者.

在两端均将该类绑定到vue.prototype上,此时两端就可以通过如下方式通信了.

1
2
3
4
5

// 桌面
desktop.$poster.post( 'drag-start', {type:'title'})
// H5
H5.$poster.on('drag-start', item => {})

现在用户可以从左侧工具栏中拖拽一个组件,而H5也可以知道拖拽的是哪种组件了.
拖拽时,还必须知道拖拽的位置.因为曾经写过一个基于dom的拖拽排序组件@redbuck/sorter.所以还算驾轻就熟.检测鼠标位置,插入一个占位元素.鼠标拖拽位置变化时,同步移动占位元素在排序元素中的位置.拖拽结束,将占位元素移除,并将其索引与类型post给父页面.

父页面接到插入组件的事件.就可以来着手生成组件的流程了.
仔细研究组件后,发现有一些流程是通用的,区别只在于其承载的内容.而内容是框架不需要关心的.这里就存在了抽象的余地.
一个组件,从生到死,有创建,修改,校验,清理的过程.
创建一个组件,需要为它生成一个唯一的标识,为后续的操作提供抓手.
还需要为它生成默认值.
当修改组件的配置时,要为它的配置提供说明.
当提交这个组件的时候,要对它的值进行校验.
删除只要用到它的标识即可.
这些步骤是所有组件都需要做的.
因此我们可以用类继承+工厂方法的的模式来做.
使用一个Proto类作为父类.提供上述方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Proto{
type = 'proto'
name = '原型'
create(tool) {
return {
key: rdm(),
name: this.name,
type: this.type,
config: this.getDefaultConfig(tool)
}
}
getDefaultConfig() {
return {}
}
}

每个类都提供自己的getDefaultConfig方法.创建时,就可以生成不同的组件配置对象了.