一、引言
6月1日接到公司项目任务让我完成一个DIY小程序,也就是小程序装修方案。接手到项目后首先查了各大公司的DIY解决方案,最后经过筛选确定了选择有赞作为参考。
**完成的功能:**用户通过简单的拖拽可以实现对页面的编辑,可以自定义组件的高度,样式等。最后小程序展示出用户编辑的效果。话不多说先上图,做出来的效果:
所用技术方面:
- PC端:Vue + element UI + vuedraggable
- H5端:Uni-App + uview
- 微信小程序:Uni-App + uview
H5端和微信小程序我是采用uni-app进行开发只需要进行一些条件编译就可以实现一套代码两端通用,比分开写节省很多时间,兼容性方面也很好。UI库方面用的今年4月新发布的uview,功能也十分强大。
二、实现思路
架构图:
如上图所示。PC端(用户编辑端)分为三个部分左侧基础组件栏、中部效果展示主体区域、右侧操作菜单栏。
H5端用到的就是动态组件
<view v-for="(item,index) in comArr" :key="index">
<component :is="item.name" :id="'com'+item.id" :comsdata="item.comsdata"></component>
</view>
小程序端因为不支持动态组件所以我采用的v-if进行判断
<view v-for="(com,comindex) in comslist" :key="comindex">
<component v-if="com.name=='grid'" is="grid" :comsdata="com.comsdata"></component>
</view>
1、实现组件拖入
左侧基础组件数据结构如下:
{
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的默认事件,否则drop事件也不会触发。允许用户放下组件后我们需要告诉iframe,我们把这个组件给你了,PC端与iframe通信我用到的就是postMessagePC端代码如下:
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;
}
})
2、实现组件插入位置
效果如下:
待添加区域在轮播图组件上方放置
待添加区域在轮播图组件下方放置
要想实现这一功能,首先肯定要拿到已添加组件的高度,并且计算出待添加组件到已添加组件的上方还是下方,这样才能确定是在已添加组件的上方还是下方插入待添加组件。那么有接下来三个问题需要解决
- 获取已添加组件的高度
- 判断鼠标在已添加组件上部分还是下部分
- 在上方或下方插入待添加组件
1.首先获取已添加组件高度,这个不难,我用的uni-app开发,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端代码如下
<div class="mask-mode" :style="'height:'+iframeheight+'px'" @dragenter="comsenter" @dragover="comsmove" @dragleave="comsleave" @drop="drop">
<div ref="comcopy" @dragover="addcommove($event,index)" @dragleave="addcomleave" v-for="(item,index) in comaddend" :key="index" :style="'width:100%;height:'+item+'px;z-index:9999'"></div>
</div>
那么接下来判断鼠标在组件的上半部分和下半部分就简单多了,来个简易图帮助理解
判断鼠标距离组件顶部的距离是否超过组件高度的一半来确定鼠标是在组件的上部分还是下部分,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添加删除时候组件选择细节处理等等。解决拖拽的难题剩下的都是一些细节问题了。如果有做类似项目的可以先写出来一个组件,把功能都完成后剩下的就往上堆组件的事了。