最近的工作主要内容就是开发组件,这里记录一下经常开发的递归组件。
一、前言 转眼间,从学校到职场已经快一年的时间了。加入掘金到现在也有两年多的时间。从只会看别人的文章到现在终于能写下自己的第一篇文章了。工作中的时常会感觉到迷茫和焦虑,希望在职场的自己仍能保持对学习的热情和对技术的热爱!
二、组件演示
介绍
最近在参与开发项目组的类BI工具,不得不说真的是学习到太多东西啦!因为BI工具主要涉及到数据处理与操作。有时候的过滤条件比较复杂,需要根据不同的业务需求与筛选条件需求自定义过滤条件。
演示
用户可以自己组合且条件或者或条件。
三、功能点梳理 开发这样的组件的时候需要提前梳理好功能点。我常常会使用印象笔记将每个功能点梳理出来,然后对功能点进行排序,最后依次完成组件的功能(这样开发效率真的很高)。
组件的功能如下:
- 添加第一条筛选条件是一个普通的数组结构。
- 从添加第二条筛选条件开始构造树结构。
- 选同级的两条筛选条件进行合并操作,并生成条件
- 合并之后下级的子节点合并再次生成条件(该条件与父级的条件互斥,如果与父节点条件相同就没有意义的哈)
- 点击“切换”图标,会取消当前的条件选项,也会取消当前的层级
四、实现
4.1从文件结构说起
4.2编码的核心其实在操作树结构
- 客户端需要的数据结构为:
textdata = [
{
Group: "group1",
GroupRel: "and",
children: [
{
Group: "group2",
GroupRel: "or",
},
{
Field: "",
FieldDataType: "",
check: false,
edit: false,
isshow: true,
index: 2,
keyword: "",
operator: ""
},
{
Field: "",
FieldDataType: "",
check: false,
edit: false,
isshow: true,
index: 1,
keyword: "",
operator: ""
}
]
}
]
- 服务端需要的数据结构为:
{
Field: "birthday"
FieldDataType: "date"
Group: "group1"
GroupAndOr: "或"
IsFirst: true
Keyword: ""
Operator: ""
},
{
AndOr: "且"
Field: "age"
FieldDataType: "number"
Group: "group1"
GroupAndOr: "或"
Keyword: ""
Operator: ""
},
{
Field: "name"
FieldDataType: "string"
Group: "group2"
GroupAndOr: "或"
IsFirst: true
Keyword: "11"
Operator: "包含"
}
4.3用js的方式渲染html页面
判断树的节点是否存在孩子,有孩子节点就递归遍历孩子节点,否则直接渲染当前筛选条件。
<div>
<ng-container *ngFor="let item of ccfilterObj">
<div class="filtertopbox" *ngIf="item.children">
<div class="group-name">
<div class="group-name--name">
<span>{{item.GroupRel}}</span>
<span (click)="filterChangeFilter(item)">
<i class="iconfont iconqiehuan"></i>
</span>
</div>
</div>
<div class="group-con">
<!--递归-->
<app-myfilter [ccfilterObj]="item.children" (deleteEvent)="deleteFun($event)"
(checkChange)="checkChangeFun($event)" (changeGroup)=" filterChangeFilter($event)" (groupSwitchEvent)="switchChangeFun($event)"></app-myfilter>
</div>
</div>
<div class="filter-single" *ngIf="!item.children">
<!--渲染组件内容-->
</div>
</ng-container>
</div>
4.4添加筛选条件(构造树节点以及他的父级)
- 创建父节点
createFF(rela = "或", isopen = false) {
return {
Group: `group${++this.myMaxIndex}`,
GroupRel: rela,
isopen: isopen,
index: ++this.myMaxIndex,
children: []
}
}
},
- 创建子节点
//构造项{当前项,父项,}
addNewItem(index) {
let { objO, objF } = this.findObjByIndeAndFF(this.ccfilterObj, index, this.ccfilterObj);
// let maxIndex = this.findMaxIndex(this.ccfilterObj);
let addObj = {
index: ++this.myMaxIndex,
Field: "",
operator: "",
keyword: "",
isshow: true,
FieldDataType: "str",
edit: false,
check: false
}
return { objO, objF, addObj }
}
- 将子节点添加到父节点的children中
//当前添加项
addfun() {
let myindex = this.ccfilterObj[0] && this.ccfilterObj[0].index;
let { objO, addObj, objF } = this.addNewItem(myindex || 0);
return { objO, addObj, objF }
}
//增加过滤条件
addFilterFun() {
this.cleckFun(this.ccfilterObj);
let { objO, addObj, objF } = this.addfun();
if (!objO || !objO.children) { //第一项,普通数组结构
objF.push(addObj)
} else {//大于一项,数组结构,但是只有一个元素,且元素对象为树结构
let temparr = this.findSwitchObj(this.ccfilterObj);
temparr.forEach((item) => {
let { objO, addObj, objF } = this.addfun();
item.children.push(addObj);
})
}
if (this.ccfilterObj.length > 1) {//替换第一项为数组结构
let obgFF = this.createFF("或", true);
obgFF.children = JSON.parse(JSON.stringify(this.ccfilterObj));
this.ccfilterObj.splice(0, this.ccfilterObj.length, obgFF);
}
}
//寻找switch为true的项
findSwitchObj(arr, res = []) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].isopen) {
res.push(arr[i]);
}
if (arr[i].children) {
this.findSwitchObj(arr[i].children, res);
}
}
return res;
}
4.5合并筛选条件(注意合并之后的条件与父级条件相斥)
mergeFun() {
//找出当前选中项
let resCheck = this.findObjByCheck(this.ccfilterObj);
let resCheckInfo = resCheck.map((item) => {
return this.findObjByIndeAndFF(this.ccfilterObj, item.index, this.ccfilterObj);
})
let objFF = resCheckInfo[0].objF;//选中项的父项
let ppObj = this.createFF(objFF.GroupRel == "或" ? "且" : "或", false);//为选中项创新的父级项
let minIndex = Math.min.apply(null, resCheckInfo.map((item) => {
return item.objF.children.findIndex(
(idtem) => {
return idtem.index == item.objO.index
}
)
}))
//删除
resCheckInfo.forEach((it) => {
it.objO.check = false;
ppObj.children.push(it.objO);
it.objF.children.splice(
it.objF.children.findIndex(
(item) => item.index == it.objO.index
),
1
);
});
objFF.children.splice(minIndex, 0, ppObj);
this.ismergeDisable = true;
}
4.6取消合并
changerel(item) {
item.GroupRel = item.GroupRel == "且" ? "或" : "且";
this.dealCancel(this.ccfilterObj);
}
dealCancel(arr) {
for (let item of arr) {
if (item.children) {
let fRel = item.GroupRel;
let equalG = item.children.filter(
it => it.Group && it.GroupRel == fRel
);
if (equalG.length != 0) {
equalG.reverse().forEach(it => {
let data = it.children;
let ind = item.children.findIndex(
ite => ite.index == it.index
);
item.children.splice(ind, 1);
data.reverse().forEach(e =>
item.children.splice(ind, 0, e)
);
});
} else {
let noEqualG = item.children.filter(
it => it.Group && it.GroupRel != fRel
);
noEqualG.forEach(it => this.dealCut([it]));
}
}
}
}
五、处理树结构常用的递归工具函数
1.获取任意节点的父节点以及当前节点
//根据index找到当前项,并且返回他的父级
findObjByIndeAndFF(arr, index, objF) {
let obj = { objO: null, objF: objF };
for (let i = 0; i < arr.length; i++) {
if (arr[i].index == index) {
obj.objO = arr[i];
break;
} else if (arr[i].children) {
obj = this.findObjByIndeAndFF(arr[i].children, index, arr[i]);
if (obj.objO) break;
obj.objF = objF;
}
}
return obj;
}
2.获取树节点中的最大层级树
//获取最大层级树
getMaxDeep(node) {
if (!node.children || node.children.length === 0) {
return 1
}
const maxChildrenDepth = [...node.children].map(v => this.getMaxDeep(v))
return 1 + Math.max(...maxChildrenDepth)
}
3.取出树结构中的每一项
//将树结构每一项拿出来
makeTreeToArr(arr, res = []) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].children) {
let tempp = JSON.parse(JSON.stringify(arr[i]));
delete tempp.children;
res.push(tempp);
this.makeTreeToArr(arr[i].children, res);
} else {
res.push(arr[i])
}
}
return res;
}
4.将同一层级的数据取出来并归类
//将树结构转化为二维数组
toDoubleArr(arr) {
let deeparr = new Set(arr.map((item) => {
return item.deep;
}))
let deeparrD = [...deeparr];
let res = [];
for (let i = 0; i < deeparrD.length; i++) {
let temparr = []
for (let j = 0; j < arr.length; j++) {
if (arr[j].deep == deeparrD[i]) {
temparr.push(arr[j]);
}
}
res.push(temparr);
}
return res;
}