最近由于项目的需求表单里需要用到下拉树选项,但iview里面并没有这个组件,所以我基于iview的select组件和tree组件进行了二次封装,实现了这个组件。这里面也借鉴了这位朋友的思路https://www.jianshu.com/p/0ce52f43864e不过感觉他有很多坑都没有仔细说明,所以下面我将的比较详细
先说下我所实现的功能:
1.点击选择并显示到select
2.修改回填时根据回填的id展示对应的name,并在树中选中所回填的子节点且展开这个子节点所有的父节点
话不多说,上代码分析。
<FormItem label="业务组">
<selectTree
:data="formItem.groupId"
v-if="panel.panelVisible"
@changeModel="backfill"
:width="'240px'"
:tree="businessGroupTree"
></selectTree>
</FormItem>
上面代码的data属性就是表单中需要双向绑定的的值,width是调整宽度,而tree属性则是需要传入的线性数组,注意不是树形结构的数组,在这个组件中会将传入的线性数组自动转换成树形结构。
这里注意表单一般要默认清空值,所以我用了v-if来清空上一次的选择的数据,changeModel是子组件返回的函数。
下面是组件的html
<template>
<div>
<Select size="small" v-model="bindData" :style="{width:realWidth}">
<Option
v-show="false"
v-for="(item,index) in optionList"
:key="index"
:value="item.value"
>{{item.label}}</Option>
<Tree :data="suitableTreeData" @on-select-change="handleSelectChange"></Tree>
</Select>
<Button ref="optionClick" v-show="false"></Button>
</div>
</template>
<script>
上面的代码中,我用v-show将option隐藏起来,在后面加上iview的 tree组件,用的是最基础的版本,如果想要扩展可以再自己添加方法二次封装。
下面是js代码
props: {
data: {
type: String
},
tree: {
type: Array,
default: () => []
},
width: {
type: String | Number
}
},
data() {
return {
bindData: this.data,//父组件传入的需要双向绑定的值
optionList: [],//Select组件的option需要使用v-for遍历的数组
parentArr: []//这个数组是保存修改时回填所需要展开的所有父节点id
};
},
接下来是所需要用的方法
首先需要将线性数组转化成树结构数组
setTreeData(source) {//将后台数据转化成treeData
let cloneData = cloneDeep(source); // 对源数据深度克隆
return cloneData.filter(father => {
// 循环所有项,并添加children属性
let branchArr = cloneData.filter(child => father.id == child.parentId); // 返回每一项的子级数组
branchArr.length > 0 ? (father.children = branchArr) : ""; //给父级添加一个children属性,并赋值
return father.parentId == ""; //返回第一层
});
},
然后将树数组再转话成iview所需要的格式
getTree(tree = [], selectId, parentIdArr) {//将转化的treeData转变成iview格式的TreeData
//tree是需要传入的树数组,selectId和parentIdArr是修改回填时所需要用的参数,分别表示回填时所填入的id和这个id所有的父级节点的id
const _this = this;
let arr = [];
// let pArr=parentIdArr
if (!!tree && tree.length !== 0) {
tree.forEach(item => {
let obj = {};
obj.title = item.name;
obj.id = item.id;
obj.expand = false;
if (selectId == obj.id) {
obj.selected = true;
}
if (!!parentIdArr && parentIdArr.length !== 0) {
for(let k of parentIdArr){
if (obj.id == k) {//这里表示如果此节点的id如果与parentIdArr里面的id相等就展开此节点
obj.expand = true;
}
}
}
obj.children = _this.getTree(item.children, selectId, parentIdArr); // 递归调用
arr.push(obj);
});
}
return arr;
},
用计算属性将转换后的tree数据传给Tree组件
computed: {
suitableTreeData() {
const _this = this;
let parentarr = _this.findSelectNode(_this.setTreeData(_this.tree), _this.bindData);
return _this.getTree(_this.setTreeData(_this.tree),_this.bindData,parentarr);
},
realWidth() {
let w = this.width;
if (!w) {
w = "250px";
}
return w;
}
}
接下来是Tree组件的事件函数
handleSelectChange(data) {//选择树节点后返回的数据
let value = null; //新建select数组的值
let label = null; //新建select数组的名字
data.forEach(item => {
value = item.id;
label = item.title;
this.bindData = value;
this.optionList = [];
this.optionList.push({//将选择节点的数据push到数组中
value,
label
});
});
this.$refs.optionClick.$el.click();//这行代码是解决选择了树组件选项不能收起下拉框的问题
this.emitFn();//解决选择了节点后给父组件不能实时接收所发射的数据得问题
},
发射给父组件的函数和Select组件所需要的数据
emitFn() {//还需要再mounted里面调用一次此函数
this.optionList = this.findOption(this.bindData, this.tree);
this.$emit("changeModel", this.bindData);
},
findOption(id, tree) {//回填修改时Select所需要的option数据
let findOptionList = tree
.filter(item => {
if (item.id === id) {
return item;
}
})
.map(item => {
return {
value: item.id,
label: item.name
};
});
return findOptionList;
},
然后是回填时,找到需要展开的所有父节点的数据
findSelectNode(tree, id) {//根据子节点找到所有父节点
const _this = this;
if (!tree) {
return;
}
tree.forEach(item => {
if (item.id == id) {
if (item.parentId) {
_this.parentArr.push(item.parentId);//如果找到子节点push父节点的parentId
_this.findSelectNode(_this.tree, item.parentId);//继续向上递归找出所有父节点
}
} else {
if (item.children) {//如果当前层级找不到,存在子节点继续递归
_this.findSelectNode(item.children, id);
}
}
});
return _this.parentArr;//找到第一层返回所有父节点
},
以上就是我所有的代码和思路,封装这个组件里面用的递归函数比较多,还是花了不少时间,所以也想分享出来让大家少踩些坑,所以写的比较详细,篇幅比较长。这是我第一次在简书上分享,如果对大家有所帮助还希望能点个赞,哈哈