需求
项目页面
PC最新画布信息
移动端同步后的画布信息
点击手机图标,将PC端信息同步到移动端
需求分析
- 只同步共有组件,私有组件不同步
- 利用递归解决布局组件嵌套问题
- 新增的节点若存在子元素,只复制共有组件信息
- 组件拖拽导致位置变化,排序问题
代码开发
算法设计
简化描述:将pc信息同步到移动端
- 树节点同层比较执行(filterPublicComponents)方法
- pc、移动端分别存储该层节点共有组件信息Map(publicNodesMap)、私有组件信息Map(privateNodesMap)
- 遍历PC端共有组件节点Map(newPublicNodesMap),
- 如果移动端存在该节点执行更新操作(handelSameNode),递归遍历
- 如果移动端不存在该节点则执行新增操作(handelAddNode)递归遍历
- 将上面节点依次push到一个新的缓存数组(transformedNodeTree)保证位置顺序相同
- 遍历移动端私有组件信息(oldPrivateNodesMap)根据下标依次插入到缓存数组(transformedNodeTree)
- 返回缓存数组(transformedNodeTree)并赋值给移动端该层级组件节点 OK,搞定
伪代码
// 同步pc移动端画布信息
transformComponentsInfo(canvasType){
if (!canvasType) return
if (this.componentsInfo.length === 0 && this.componentsInfoMobile === 0) return
const newNodeTree = canvasType === 'pc' ? 'componentsInfo' : 'componentsInfoMobile'
const oldNodeTree = canvasType === 'pc' ? 'componentsInfoMobile' : 'componentsInfo'
this[oldNodeTree] = this.traverseComponentsList(this[newNodeTree],this[oldNodeTree],canvasType)
},
/**
* 生成共有组件Map(id->node),生成私有组件Map(index->node)
* 目的:为了方便排序,过滤掉私有组件节点,待共有节点排序完成后,再根据index插入队列中
* @param {*} nodeTree 组件树
* @return {*} publicNodesMap: 共有组件Map; privateNodesMap:私有组件Map
*/
filterPublicComponents(nodeTree) {
// index->node map
const publicNodes = []
let privateNodesMap = new Map()
nodeTree.forEach((node,index)=>{
if (node.support !== 'all') {
privateNodesMap.set(index,node)
} else {
publicNodes.push(node)
}
})
const publicNodesMap = mapFromArray(publicNodes,'id')
return {publicNodesMap,privateNodesMap}
},
/**
* 同步数据信息新增、更新、删除(同级比较)
* @param {*} newNodeTree // 已更改端的组件树
* @param {*} oldNodeTree // 待同步端的组件集树
* @param {*} canvasType // 当前更改端标识
* @return {*} 同步后的组件树
*/
traverseComponentsList(newNodeTree,oldNodeTree,canvasType){
if (!Array.isArray(newNodeTree) || !Array.isArray(oldNodeTree)) return
const { publicNodesMap:newPublicNodesMap} = this.filterPublicComponents(newNodeTree)
const {
publicNodesMap:oldPublicNodesMap,
privateNodesMap:oldPrivateNodesMap
} = this.filterPublicComponents(oldNodeTree)
const transformedNodeTree = [] // 信息同步后的组件集合
// 根据最新节点匹配数据信息
// map类型根据key找到目标节点,无需双层嵌套循环寻找比对,提高搜索效率
// 根据最新端共有节点信息来插入,简化删除操作
for (const key in newPublicNodesMap) {
const newNode = newPublicNodesMap[key]
const oldNode = oldPublicNodesMap[key]
if (newNode.support === 'all') {
if (oldNode) { // 更新组件节点
this.handelSameNode(newNode,oldNode,canvasType)
transformedNodeTree.push(oldNode)
} else { // 新增组件节点
const addNode = this.handelAddNode(_.cloneDeep(newNode),'',canvasType)
transformedNodeTree.push(addNode)
}
}
}
// 私有组件根据原先位置依次插入队列中
oldPrivateNodesMap.forEach((node,index) => {
transformedNodeTree.splice(index,0,node)
});
return transformedNodeTree
},
// 处理新增节点
handelAddNode(newNode,oldNode,canvasType){
// 同步属性
handleInfoSynchronous(newNode,'',canvasType)
if (this.isLayout(newNode)) {
handleLayoutChildren(newNode, (infoChildren) => {
// 遍历子节点,过滤掉私有组件
for (let i = infoChildren.length -1; i>=0; i-- ) {
const infoChild = infoChildren[i]
if (infoChild.support === 'all') {
this.handelAddNode(infoChild,'',canvasType)
} else {
infoChildren.splice(i,1)
}
}
})
}
return newNode
},
// 处理双端都存在的节点
handelSameNode(newNode,oldNode,canvasType){
handleInfoSynchronous(newNode,oldNode,canvasType)
if (this.isLayout(newNode)) {
handleLayoutChildren(newNode, (newNodeChildren) => {
handleLayoutChildren(oldNode, (oldNodeChildren,parent) => {
parent.children = this.traverseComponentsList(newNodeChildren,oldNodeChildren,canvasType)
})
})
}
},