创新实训:模块介绍--分组

前端通过连接后端核心库,为客户提供分组的功能

当通过ctrl模式一次性选中多个组件时,可以通过右侧的功能栏的分组按钮进行分组。完成分组中,组内元素可以有统一的移动和其他特性

1.功能介绍

关于分组有两个功能:link和group

group的实现基于link。

link:在组件之间建立父子关系。之后父组件的行动会影响子组件,但是子组件的行动不会影响父组件。

group:在组件之间建立分组关系。组内元素可以一起移动。

2.功能实现分析

可以看出 link是实现group的基础,但是link和group之间又有许多不同之处。

通过后端的三个信号,实现这两个功能。

link:

link <id1:str> <id2:str>
将id1作为id2的儿子。

unlink:

unlink <id1:str> <id2:str>
移除id1与id2间的父子关系,顺序任意。
即使id1和id2间没有父子关系,也能返回成功。

cover_children

cover_children
使矩形自适应包裹所有子组件。

link的实现可以依靠信号,但是group的实现需要前端进行许多添加

group实现分析

前端向后端请求建立一个rect,并在rect和所有组内元素之间建立link关系,同时隐藏rect,这样可以做到点击空白处并不会唤醒分组。

ungroup的时候,将rect进行删除。

整体逻辑较为清晰,但是在实现的时候会遇到各种各样的问题

3.功能详细介绍

link界面:暂时通过右侧的功能栏进行实现。

实际效果如下
在这里插入图片描述该右侧栏的主要功能是为用户提供unlink和link的端口。
并提供组件的树形结构视图。

用户通过input框要求的指令进行link和unlink,并可以通过选中组件查看组件的树形结构视图。

group的功能

通过group按钮进行分组,并通过ungroup按钮进行解除分组后,分组后选中元素会首先定位到整个分组,再次点击后层级游标向下移动。
eg:

样例为两层group,第一次点击右下角组件时,如图所示
再次点击时,游标会在树形结构中进行下滑。
移动后外层包裹的层层rect会进行自动放缩

4.具体实现介绍

A.组件树的建立

需要用树形结构来保存组件的树形逻辑。

class Module_tree {
    this.head=[]
    this.aliasList={}
    this.nameList={}
    this.nodeList={}//id not alias
}

参数说明:

head为整张图的结构,为森林结构

aliasList和nameList为id<–>alias的双向映射,通过为用户提供别名而避开繁杂的id。

nodelist为id到组件在head中index的索引

B.向组件树中添加组件

在组件创建之后,需要在组件树种进行注册。同时为组件创建别名并注册。

addModule(id,type){
    let counter=++aliasCounter[type];
    this.aliasList[type+"_"+counter]=id;
    this.nameList[id]=type+"_"+counter;
    // console.log(this.aliasList);
    // console.log(this.nameList);
    let item={};
    item['id']=this.nameList[id];
    item['children']=[];
    item['tid']=this.head.length;
    item['father']=this.head.length;
    this.nodeList[id]=this.head.length;
    this.head.push(item);
    // console.log(this.head);
}

C.在组件之间建立link

可以通过别名和id进行设置,不过只是进行简单的替换之后调用即可。

link后,将子组件的索引保存在父组件的children属性中,并保存父组件的索引,方便进行双向查找

linkInTree(id1,id2){
    id1=this.nameList[id1];
    id2=this.nameList[id2];
    let module1="";
    let module2="";
    //找到两个节点
    for(var i=0;i<this.head.length;i++){
        if(this.head[i]['id']===id1){
            module1=i;
            break;
        }
    }
    for(var i=0;i<this.head.length;i++){
        if(this.head[i]['id']===id2){
            module2=this.head[i];
            this.head[module1]['father']=module2['tid'];
            module2['children'].push(this.head[module1]['tid']);
            break;
        }
    }
    console.log(this.head)
}

D.提供某一个组件的完整树形结构

分为两个部分,第一个部分为寻找祖先,第二个部分为从祖先开始进行记录,记录时通过bfs对node和link进行记录,并调用echart进行图形绘制。

getRelationById(id){
    let now=this.nodeList[id];
    // console.log(now);
    // console.log(this.head);
    while(true){
        if(this.head[now]['tid']!==this.head[now]['father']){
            now=this.head[now]['father'];
        }else{
            break;
        }
    }
    let msg=this.getTree(now,0);
    return msg
    // console.log(id);
    // console.log(list)
}
getTree(now,width){
    let linkList=[];
    let nodeList=[];
    let queue=[];
    let node={id:this.head[now]['id'],width:width,tid:this.head[now]['tid']}
    queue.push(node);
    while(queue.length!==0){
        let node=queue[0];
        queue.splice(0,1);
        nodeList.push(node);
        let tid=node['tid'];
        let children=this.head[tid]['children'];
        for(let i=0;i<children.length;i++){
            let child=this.head[children[i]];
            let nod={id:child['id'],width:node['width']+1,tid:child['tid']};
            linkList.push({source:node['id'],target:nod['id']});
            queue.push(nod)
        }
    }
    let msg={node:nodeList,link:linkList};
    return msg;
}

E.删除组件后的消除动作

删除一个组件后,需要对它的儿子和父亲进行处理后再进行删除。将父亲的子节点指向自己的儿子,同时将儿子的子节点指向自己的父亲,需要注意的是,当自己没有父亲的时候,儿子的父亲改为指向自己(这是一个特判)

deleteById(id){
    let now=this.nodeList[id];
    let father=this.head[now]['father']
    if(father!==now){
        father=this.head[father];
        for(let i=0;i<this.head[now]['children'].length;i++){
            father['children'].push(this.head[now]['children'][i])
        }
        for(let i=0;i<this.head[now]['children'].length;i++){
            if(father['children'][i]===this.head[now]['tid']){
                father['children'].splice(i,1);
            }
        }
    }
    let children=this.head[now]['children']
    for(let i=0;i<children.length;i++){
        let child=this.head[children[i]]['tid'];
        child=this.head[child];
        child['father']=father===now?child['tid']:father['tid'];
    }
    this.head[now]["tid"]=undefined
    this.head[now]["father"]=undefined
    this.head[now]["children"]=undefined
    this.head[now]["id"]=undefined
    console.log(this.head)
}

至此:我们提供了link给group所需要的大部分功能,但是还有细节需要进行处理。

5.细节处理

A.建立group的时候,需要创建一个rect并进行隐藏。

由于group没有专用的指令进行执行,我们需要修改一部分代码。通过在创建rect的时候添加一个多余的信号show标识该组件是否为group所用rect进行区分,并对addAction的after进行修改。

this.msg['show']=this.cmd['show'];
if(!this.cmd['show']){
    let node=document.getElementById(this.id);
    let parser=new cssParser();
    parser.parseStyle(node.getAttribute("style"))
    parser.updateStyle({display:"none"})
    parser.updateStyle({'stroke-width':0})
    node.setAttribute("style",parser.get());
    createModule(this.msg,false);
    addModuleToTree(this.id,this.type);
    let coreList=getCoreList();
    for (var i=0;i<coreList.length;i++){
        linkByGroup(coreList[i],this.id);
    }
    P("cursors",{ids:[this.msg['id']]})
    P("cover_children",{})
    return;
}
createModule(this.msg);//需要修改
P("cursors",{ids:[this.msg['id']]})
addModuleToTree(this.id,this.type);

这样我们就完成了隐藏功能,同时对cover进行了修改。

B.点击组件后所选中的组件

这里有点麻烦,因为我们需要考虑当前祖先情况,同时需要避开link而只考虑group的rect。

思路:我们先构造一个方法,获取所点击组件的所有祖先,然后遍历祖先两轮,第一轮先遍历是否有祖先被选中,如果有的话,选中下一个group节点(当选中节点为自己是保持不变)

如果没有的话,第二轮遍历从祖先开始,寻找第一个show=false(group_rect)的节点

if(that.move){
    that.move=false;

}else if(that.isCore===false) {
    let ancestor=undefined;
    let ancestorList=getAncestorAll(that.g_id);
    for(let i=0;i<ancestorList.length;i++) {
        let el = getModuleByGid(ancestorList[i])
        if(el.isCore){
            i++;
            while(i<ancestorList.length){
                el=getModuleByGid(ancestorList[i])
                if(!el.show)ancestor=el.g_id;
                break;
            }
            if (ancestor===undefined){
                ancestor=that.g_id;
            }
            pushElementInQueue([ancestor]);
            return;
        }
    }
    for(let i=0;i<ancestorList.length;i++){
        let el = getModuleByGid(ancestorList[i])
        if(!el.show)ancestor=el.g_id;
        break;
    }
    if (ancestor===undefined){
        ancestor=that.g_id;
    }
    // console.log(ancestor);
    pushElementInQueue([ancestor]);

!!!这里要多进行一个判断,移动的前提条件为当前图形被选中(而此时图形未被选中,选中的是它的祖先)

6.总结

分组是一个很重要的功能,它提供了对多个组件同时进行操作的可能性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值