表单设计器不同端组件信息树同步:两个对象数组树信息同步

81 阅读2分钟

需求

image.png 项目页面

PC最新画布信息 image.png 移动端同步后的画布信息

image.png

点击手机图标,将PC端信息同步到移动端

需求分析

  1. 只同步共有组件,私有组件不同步
  2. 利用递归解决布局组件嵌套问题
  3. 新增的节点若存在子元素,只复制共有组件信息
  4. 组件拖拽导致位置变化,排序问题

代码开发

算法设计

简化描述:将pc信息同步到移动端

  1. 树节点同层比较执行(filterPublicComponents)方法
  2. pc、移动端分别存储该层节点共有组件信息Map(publicNodesMap)、私有组件信息Map(privateNodesMap)
  3. 遍历PC端共有组件节点Map(newPublicNodesMap),
    • 如果移动端存在该节点执行更新操作(handelSameNode),递归遍历
    • 如果移动端不存在该节点则执行新增操作(handelAddNode)递归遍历
    • 将上面节点依次push到一个新的缓存数组(transformedNodeTree)保证位置顺序相同
  4. 遍历移动端私有组件信息(oldPrivateNodesMap)根据下标依次插入到缓存数组(transformedNodeTree)
  5. 返回缓存数组(transformedNodeTree)并赋值给移动端该层级组件节点 OK,搞定

伪代码

image.png

// 同步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)
      })
    })
  }
},