首先这个整体在活动详情这个页面显示。这个页面是这个项目得亮点也是难点,
先是pageleft拖拽组件,获得组件数据component改变拖拽状态dragactive,然后在预览页面pagereview中添加到预览位置(先获得拖动鼠标距离预览区top距离,进行鼠标pageY+滚动轴的距离-固定头部位置,会得到this.$refs.pageView.scrollTop + event.pageY - viewWrapTop。接着鼠标在该组件上部就会预览位置添加到上部,下部就是同理,所以要获取(当前组件高度)/2,当遍历state中h5组件高度,前组件高度就用后一个-前一个即可,如果大于一半,那么就把当前索引+1,赋值给addinddex,向H5页面发送预添加组件,如果拖拽组件离开h5,那么就add index=null,然后删除预览位置,拖拽结束,判断addindex是否存在,存在就传递参数调用方法,生成组件,向h5发送更新数据(pagedata)。结束后会更新pagedata
所有要在h5改变的都用到了跨源通信,都要在crs中改变才行。
这是拖拽结束
页面渲染的时候会发送请求获取页面数据,而且还要给crs发送当前这个数据,让crs做更改,然后更新页面数据pagedata和给h5把当前页面数据发送。
created() {
this.init()
},
methods: {
init() {
this.getData()
},
getData() {
let pageId = this.$route.query.id
if (pageId) {
getCmsPageById(pageId).then(({data}) => {
this.postDataToH5(data)
})
}
},
postDataToH5(data) {
if (data && data.componentList) {
data.componentList.forEach((item) => {
if (item.data.validTime && typeof item.data.validTime === 'string') {
item.data.validTime = JSON.parse(item.data.validTime)
}
// 获取的组件数据无id则手动设置随机id
if (!item.id) {
const id = createRandomId()
item.id = item.data.component + '-' + id
}
})
this.$store.commit('UPDATE_COMPONENT', {data})
this.$store.commit('VIEW_UPDATE')
}
},
难点
1.拖拽得实现和拖拽预览位置得确定。
2.拖拽之后在crs中展示 这里使用了跨源通信。
3.后端传递数据,利用vuex管理组件得数据和页面数据。
4.crs内嵌cms,用了iframe
总体就是通过左侧对组件得拖拽实现在crs中得页面组件得渲染和预览展示,中间使用了Window.postMessage()方法进行跨源通信。(组件是沟通两个系统的桥梁
先说这个页面整体布局吧
左侧:折叠面板-组件列表
中间 :crs-h5预览 通过iframe内嵌的(知道端口号。
右侧:编辑区域
<div class="home">
<!-- 搭建框架 -->
<PageHead />
<PageLeft />
<PageView />
<PageRight />
<!-- 组件的公共配置内容 -->
<!-- 上传图片组件 -->
<UpLoadImg :dialog-image-visible.sync="dialogImageVisible" @upLoadImgSuccess="upLoadImgSuccess" />
</div>
首先看后端返回得数据。对组件的修改就是对组件jason对象得修改!
返回的数据主要就是有
图片广告、图文导航、公告、魔方、文本
1.组件的名字、这个组件下得组件名字
2.组件有效时间 因为有秒杀时间
3.图片列表
4.组件得样式,字体大小,背景色,组件里文本内容、行高、行距
const componentlist = [
{
title: '基础组件',
components: [....]
}
{
name: '图片广告',
iconClass: 'cms-icon-ad',
maxNumForAdd: DEF_MAX_NUM,
data: {
component: 'Carousel',
//秒杀组件 有效时间
validTime: [],
layout: 'swiper',
imageList: [
{
link: null,
imageUrl: '',
text: '导航1'
}
],
//图片得详情样式
imageMargin: 0,
isDefaultMargin: 0,
marginSize: [0, 0],
isBorderRadius: 0,
radius: 0,
backgroundColor: '',
piclist: []
// imageRadius: 'square',
// imageStyle: 'normal',
// imageMargin: 0,
// pageMargin: 0
}
/*
* component 组件类型标识
* validTime 组件生效时间
* layout 组件布局方式
* text 浮层标题名称
* backgroundColor 背景颜色
* textColor 字体颜色
* columnPadding 上下边距
* rowPadding 左右边距
* lineNumber 单行个数
* borderRadius 圆角大小
* imageList 图片列表
*/
{
name: '图文导航',
maxNumForAdd: DEF_MAX_NUM,
iconClass: 'cms-icon-nav',
data: {
component: 'ImageNav',
validTime: [],
layout: 'pic',
text: '',
backgroundColor: '#FFFFFF',
textColor: '#323233',
columnPadding: 20,
rowPadding: 20,
lineNumber: 4,
borderRadius: 0,
imageList: [
以上是组件得数据。
接下来就先写左侧pageLeft,整个是一个折叠面板,遍历数据嘛,联合vuex了,看store里得数据。
state: {
// 当前设置内容 1-页面;2-组件
setType: 1,
// 上传图片弹出框组件是否显示
dialogImageVisible: false,
// 上传图片成功后的回调事件
upLoadImgSuccess: null,
// 当前搭建的页面数据,保存时也是保存该对象
pageData: JSON.parse(JSON.stringify(emptyPageData)),
// 当前选中的组件id,根据该id显示对应的编辑组件及预览时添加选中状态
activeComponentId: null,
// 当前是否正在拖动组件到页面
dragActive: false,
// 当前正在拖动的组件对象
dragComponent: {},
// 拖动左侧组件时当前要添加到的索引位置
addComponentIndex: null,
// 搭建模块跨源通信 - H5页面高度
previewHeight: '',
// 搭建模块跨源通信 - H5页面各组件top值, 计算拖拽时组件要拖动到的位置
componentsTopList: '',
wxParams: JSON.stringify({isLogin: true}) // h5页面参数
},
主要得也就是:
1.当前页面的数据pageData
2.是否正在拖动组件 dragActive
3.正在拖动得组件 dragComponent
4.拖动组件要添加到中间得索引位置 addComponentIndex
5.h5页面的两个属性:h5页面高度+h5各组件得top值。
这里我们采用 HTML5 拖拽特性,为 DOM 节点添加 draggable 属性后即可自由拖动:
<ul class="component-list"> <li v-for="(component,size) in item.components" :key="size" //判断组件是否被选中 :class="draggableEnable(component) ? 'drag-enabled' : 'drag-disabled'" //判断组件是否可拖拽 :draggable="draggableEnable(component)" //拖拽时候 逻辑通信 @dragstart="onDragstart(component, $event)" //拖拽之后 @dragend="onDragend($event)" >
遍历组件,动态判断组件是否可以拖拽。
draggableEnable(component) { let curNum = this.componentMap[component.data.component] || 0 return curNum < component.maxNumForAdd }
能否被点击就是看页面上使用的组件超没超过组件的最大值,怎么获取这个使用组件得值呢?在getter中写了一个方法,记录组件使用的值,map,通过查询键就可以获得。
pageComponentTotalMap: (state) => { let map = {} let cList = state.pageData.componentList || [] let cName cList.forEach((c) => { cName = c.data.component if (map[cName]) { map[cName] += 1 } else { map[cName] = 1 } }) return map }
整个页面得数据在state得pagedata中。首先初始化pagedata,后续每次操作都会在mutation中改变pagedata中的数据。!!
初始化pageData。
const emptyPageData = {
id: '',
name: '页面标题',
shareDesc: '', // 微信分享文案
shareImage: '', // 微信分享图片
backgroundColor: '', // 页面背景颜色
backgroundImage: '', // 页面背景图片
backgroundPosition: 'top', // 页面背景位置
cover: '',
componentList: []
}
左侧组件列表拖拽实现,
拖拽现在左侧开始,然后拖拽到预览页面,离开预览页面,拖拽结束,拖拽结束根据是否有添加组件的索引值判断是否添加,是这个顺序。
拖拽开始得时候、拖拽之后的方法:
拖拽开始,拖拽的状态设置成不可拖拽state.dragActive = value,把这个组件传递过去state.dragComponent = value。
拖拽时:
onDragstart(component, event) {
console.log('开始拖动组件', component, event)
this.SET_DRAG_STATE(true)
this.SET_DRAG_COMPONENT(JSON.parse(JSON.stringify(component)))
},
拖拽结束:
是否有添加索引(因为在组件离开预览位置置index=null,
onDragend(event) {
this.SET_DRAG_STATE(false)
let addIndex = this.addComponentIndex
if (addIndex != null) {
console.log('生成组件')
this.pageChange({
type: 'add',
index: addIndex,
data: this.dragComponent
})
this.SET_DRAG_INDEX(null)
console.log(addIndex, 'addIndex')
this.VIEW_SET_ACTIVE(addIndex)
}
},
pagechange 根据传递参数,选择调用方法,向h5发送数据。
pageChange({ commit }, changeValue) {
console.log(changeValue, 'changeValue')
const commitObj = {
add: 'ADD_COMPONENT', // 新增组件
delete: 'DELETE_COMPONENT', // 删除组件
edit: 'EDIT_COMPONENT', // 编辑组件
update: 'UPDATE_COMPONENT' // 更新页面
}
commitObj[changeValue.type] && commit(commitObj[changeValue.type], changeValue)
// 向H5页面发送更改后的数据
commit('VIEW_UPDATE')
},
中间crs-h5拖拽开始、结束的方法:
左侧组件拖动到页面预览区域事件,
记录组件要添加的位置,预览位置。下面方法是添加在预添加位置上的
<div v-if="dragActive" class="preview-drag-mask" @dragover="onDragover($event)" />
<div v-if="dragActive" class="preview-drag-out" @dragover="onDragout($event)" />
获取拖拽的预览位置这里可以说是难点。首先获得鼠标当前位置,event.pageY,再减去头部固定的位置,这时候可能h5长度过大有了滚动轴,所以再加上滚动轴:
this.$refs.pageView.scrollTop + event.pageY - viewWrapTop
然后再获取拖拽组件要添加的位置的索引,遍历h5组件,
// 左侧组件拖动到页面预览区域事件
onDragover(event) {
event.preventDefault() // 设置当前区域可以被进入
const viewWrapTop = 191 // 页面预览区域距离顶部top
// 拖动距离预览区top距离
let dropTop = this.$refs.pageView.scrollTop + event.pageY - viewWrapTop
console.log(dropTop, 'dropTop')
// 获取当前拖动组件要添加位置的索引
let addIndex = 0
for (let i = this.componentsTopList.length - 1; i >= 0; i--) {
const value = this.componentsTopList[i]
const prev = this.componentsTopList[i - 1] || 0
const _half = (value - prev) / 2 // 当前组件高度的一半
if (i === 0 && dropTop <= _half) break
if (dropTop > (value - _half)) {
addIndex = i + 1
break
}
}
// 预览区域生成预添加组件
if (this.addComponentIndex === addIndex) return
// console.log('预添加组件,索引值为', this.componentsTopList, addIndex, dropTop)
this.SET_DRAG_INDEX(addIndex)
this.VIEW_ADD_PREVIEW(addIndex)
},
拖拽组件离开h5
设置添加位置索引为null,向H5页面发送删除预添加组件。
内嵌crs
<iframe id="previewIframe"
class="preview-iframe"
:src="previewSrc"
title="频道名称"
需不需要边框0不需要1需要
frameborder="0"
allowfullscreen
width="100%"
:height="previewHeight"
@load="onloadH5"
/>
previewSrc() {
return settings.decorateViewSrc + `?pageId=${this.$route.query.id || ''}&noLogin=true`
}
跨源通信
Window.postMessage()方法 跨源通信
otherWindow.postMessage(message, targetOrigin, [transfer]);
/**
* @name: 父级窗口搭建页面与H5预览页面跨源通信函数
* @param {Window} win 要接收消息的目标窗口
* @param {String} targetOrigin 指定win中可以接收到消息的origin
*/
export class Messager {
// 构造实例函数
constructor(targetOrigin) {
this.targetOrigin = targetOrigin
this.actions = {}
// 监听传送数据的实例函数
this.messageListener = (event) => {
// 判断接收的消息是否是指定的域发送、是否有监听指定的事件,符合则执行监听事件
const type = event.data && event.data.type
if (event.origin === this.targetOrigin && type && this.actions[type]) {
this.actions[type](event.data.value)
}
}
window.addEventListener('message', this.messageListener)
}
//监听函数
on(type, cb) {
this.actions[type] = cb
return this
}
/**
* @name: 发送指定名称的消息
* @param {String} type 发送的消息名称
* @param {Object} value 发送的数据
*/
//发送函数
emit(type, value) {
var win = document.getElementById('previewIframe').contentWindow
//发送消息
win.postMessage({
type, value
}, this.targetOrigin)
return this
}
// 移除消息的监听
destroy() {
window.removeEventListener('message', this.messageListener)
}
}
挂载pageview页面的时候,初始化数据
// 跨源通信对象H5数据的监听
initMessage({ commit }) {
// 监听H5预览页面高度变化
messager.on('pageHeightChange', data => {
console.log('从H5更新组件高度为', data)
let height = data.height ? data.height + 72 : 768
let list = data.componentsTopList || []
commit('UPDATE_PAGE_HEIGHT', { height, list })
})
// 监听H5预览页面数据变化
messager.on('pageChange', data => {
console.log('从H5更新组件数据为', data)
commit('UPDATE_COMPONENT', { data })
})
// 监听H5预览页面选中项id变化
messager.on('setActive', id => {
commit('SET_ACTIVE_ID', id)
commit('SET_SETTYPE', 2)
})
}
},
})