接到公司项目让我完成一个DIY小程序,也就是小程序装修方案。接手项目后首先查了一下主流DIY解决方案,最后确定了选择有赞作为参考。
完成的功能:用户通过简单的拖拽可以实现对页面的编辑,可以自定义组件的样式、展示顺序等。最后小程序展示编辑的效果。话不多说先上图,做出来的效果:
所用技术方面:
H5端和微信小程序我是采用uni-app进行开发只需要进行一些条件编译就可以实现一套代码两端通用,比分开写节省很多时间,兼容性方面也很好。UI库方面用的今年4月新发布的uview,功能也十分强大。
简易思路图:
如上图所示。PC端(编辑端)分为三个部分左侧基础组件栏、中部主体展示区域、右侧操作菜单栏。
H5端用到的就是动态组件
小程序端因为不支持动态组件所以我采用的v-if进行判断
左侧基础组件数据结构如下:
{ name:'轮播图', usedNum:0, //已经使用的个数 totleNum:3, //可使用的个数 img:'img/coms-ico/swiper.png', //未选中图片 activeImg:'img/coms-ico/swiper-active.png', //选中图片 comsName:'myswiper', //组件名 comsData:{ //组件样式 list:[ {image: '../../static/swiper/timg.jpg',title:'力拔山兮气盖世,时不利兮骓不逝'}, {image: '../../static/swiper/timg.jpg',title: '骓不逝兮可奈何,虞兮虞兮奈若何'}, {image: '../../static/swiper/timg.jpg',title: '我寄愁心与明月,随风直到夜郎西'} ], height:190, //高度 mode:'round', //提示点模式 indicatorPos:'bottomCenter', //提示点位置 effect3d:'yes', //是否3D展示 title:'yes' //是否显示标题 } },
组件拖动的关键是允许拖动,需要给每个组件设置draggable="true"
属性。我们从左侧基础组件栏选择某个组件拖入iframe(H5)中,在iframe上方释放鼠标的话会把选中的组件在iframe中添加。这个过程中需要注意的是在拖动过程中遇到iframe,拖动事件将会失效。
解决方案:在iframe上方套一个透明遮罩层,通过监听dragstart
、dragsend
事件控制遮罩层的显示与隐藏即可。系统需要知道你拖动的哪一个组件,这里也是在dragstart
事件中声明。
// 实现拖动组件 ondragStart(item,e,index){ var date = new Date() item.id = date.getTime() item.index= index e.dataTransfer.setData("coms",JSON.stringify(item)); this.dragstatus = true }, ondragEnd(e){ this.dragstatus = false },
当用户可以将组件拖入iframe上方时释放鼠标,将组件放下,iframe中添加对应的组件,用到drop
事件,drop
事件浏览器会默认不允许用户拖放,这里阻止一下系统默认事件。还有一点需要注意一定也要阻止dragover(cosmove)
的默认事件,否则drop
事件也不会触发。允许用户放下组件后我们需要告诉iframe,我们把这个组件给你了,PC端与iframe通信用到的就是postMessage
PC端代码如下:
comsMove(e){ e.preventDefault(); }, drop(e){ e.preventDefault(); var data = JSON.parse(e.dataTransfer.getData("coms")) //拿dragstart中的数据 this.Iframe.contentWindow.postMessage({ cmd: 'comdrop', params: { name:data.comsname, id:data.id, title:data.name, comsdata:data.comsdata, index:data.index }},'*'); this.dragstatus = false },
H5端在onload函数接收数据,并进行一系列相关处理,在页面中展示
window.addEventListener('message',e=>{ var params = e.data.params switch (e.data.cmd){ case 'comdrop':;break; } })
效果如下:
待添加区域在轮播图组件上方放置
待添加区域在轮播图组件下方放置
要想实现这一功能,首先肯定要拿到已添加组件的高度,并且计算出待添加组件在目标组件的上方还是下方,这样才能确定是在目标组件的上方还是下方插入待添加组件。那么有接下来三个问题需要解决
1.首先获取已添加组件高度,这个不难,H5端代码如下
var heightArr = [] this.comArr.forEach((res,i)=>{ uni.createSelectorQuery().in(this).select('#com'+res.id).boundingClientRect(data => { heightArr.push(data.height) }).exec(); })
这样就可以获取到已经添加的各个组件高度的一个数组,需要注意的是组件id不能固定,因为有的组件可以重复添加,样式高度都可能不同。
2.拿到组件高度数组后通过window.parent.postMessage(heightArr, '*');传递给父页面也就是我们的PC端编辑页,PC端接收到后在iframe蒙层上将高度数组已空白地形式渲染出来,PC端代码如下
那么接下来判断鼠标在组件的上半部分和下半部分就简单多了,来个简易图帮助理解
判断鼠标距离目标组件顶部的距离是否超过目标组件高度的一半来确定鼠标是在目标组件的上半部分还是下半部分,PC端代码如下
addComMove(e,index){ e.preventDefault(); var divObj = this.$refs.comcopy[index].getBoundingClientRect() var divHeight = divObj.y + divObj.height var nowMarginTop = e.y - divObj.y this.nowAbove.index = index if(nowMarginTop >= divObj.height/2){ this.nowAbove.status = false //false代表鼠标在组件下方 }else{ this.nowAbove.status = true //false代表鼠标在组件上方 } },
需要注意的就是组件高度与鼠标在Y轴上布局需要经过简单的处理
3.知道鼠标移动到组件上方或者下方就简单了,但是我们不能通过鼠标移动事件传递状态值,太频繁卡死的几率大。鼠标一直在组件上方状态一直未true,只有当鼠标划入组件下方时状态才会变为false,所以只需要监听status
的变化即可,PC端代码如下:
watch:{ 'nowAbove.status'(value){ this.Iframe.contentWindow.postMessage({ cmd: 'commove', params: { index:this.nowAbove.index, status:this.nowAbove.status }},'*') } },
只需要传一个索引值,和状态值在H5界面即可判断出是在组件上方插入待添加的组件还是在下方插入待添加的组件,H5端代码如下:
if(params.status){ //status为true表示在上方 false表示在下方 this.addIndex = params.index }else{ this.addIndex = params.index + 1 } this.comArr.splice(this.addIndex,0,{name:'placeHolderView'})
组件管理可以调整已添加组件的位置,我用了vuedraggable,效果如下图所示。
只需要监听change
事件,通过postMessage
将组件列表传给H5端重新渲染即可。有一点需要注意H5端重新渲染后需要再次计算一下各个组件的高度并返回给PC端
PC端代码如下:
this.Iframe.contentWindow.postMessage({ cmd: 'comListChange', params: { comList:this.list }},'*')
H5端代码如下:
this.comArr = [] this.comArr = params.comlist this.comArr.forEach((res,i)=>{ if(res.name == 'placeHolderView'){ this.comArr.splice(i,1) }else{ uni.createSelectorQuery().in(this).select('#com'+res.id).boundingClientRect(data => { heightArr.push(data.height) }).exec(); } })
H5每添加一个组件都需要重新获取页面的高度传给PC端,PC端iframe高度动态变化
var h = document.body.scrollHeight; window.parent.postMessage(h, '*');
分享只提供我一个思路!!!到这里项目基本已经完成。总体来说难点不多就是有许多坑需要填,比如drop
事件生效必须要阻止dragover
事件的默认行为、iframe添加删除时候组件选择细节处理等等。解决拖拽的难题剩下的都是一些细节问题了。如果有做类似项目的可以先写出来一个组件,把功能都完成后剩下的就往上堆组件的事了。