网上看了一圈,很多是选项层级固定死3层而不是随意多少层都可以的,还有很多讲不清楚的,填个坑,希望能帮到有缘人。
实现该效果,需要后端配合需要提供2个接口:
1.前端传入指定节点ID,返回该节点的下一层所有节点ID信息列表。
2.前端传入指定节点ID,返回该节点从顶部节点至下(到该节点)的所有节点ID列表。
级联选择器 el-cascader 使用:
<el-cascader
v-model="IdList"
:props="props"
></el-cascader>
最关键的只有这两项:v-model绑定值 和 props 配置项
一、动态加载选项
props配置:
动态加载选项不需要 :options 配置,静态的才需要。
props: {
checkStrictly: true, //是否可以选择树干节点作为选项
lazy: true, // 是否动态加载子节点
// lazyLoad加载动态数据的方法(仅在 lazy 为 true 时有效)
lazyLoad: this.loadTreeNode,
},
远程加载节点的js方法:
//加载树节点 首次加载页面时就会执行一次
loadTreeNode(node, resolve) {
console.log(node);
// 首次加载时 node为{root:true,level:0}
// node 节点数据 获取node的level字段的值
const { level } = node;
//下一层节点
const nodes = [];
//如果有子节点 或者 为根节点(即首次进入level为0)
//也有人写成 node.level == 0 作用是一样的
if (node.hasChildren || node.root) {
// 0 代表第一次请求
let nodeId = level == 0 ? null : node.value;
//这里setTimeout的目的是 显示加载动画
setTimeout(() => {
//调用后端接口 获得返回数据
let ret = this.api(nodeId);
if (ret && ret.succeeded) {
//ret.reulst为后端返回的数据
let nodes = ret.result;
// 回调渲染下一层
resolve(nodes);
} else {
//后端报错 弹窗提示失败
this.$message({
type: "error",
message: "部门层级加载失败,请联系管理员!",
});
}
}, 1);
} else {
//如果没有子节点就不发起请求,直接渲染,也避免了点击叶子节点仍然有加载动画的问题
resolve(nodes);
}
},
叶子节点指的是:没有子节点的节点,它就是最底层。
这里就需要后端提供第一个接口:
1.前端传入指定节点ID,返回该节点的下一层所有节点ID信息列表(sql查询parentGroupID为指定节点ID的数据即可)。
返回的数据需要是一个列表,后端返回的数据 List<UserGroupTreeNode> 长这样:
后端的节点定义(可参考):
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserGroupTreeNode {
/**
* 节点值
*/
private String value;
/**
* 节点标签
*/
private String label;
/**
* 是否为叶子节点(即没有子节点) true 没有子节点 false 有子节点
*/
private Boolean leaf;
}
其中value即为节点的ID,label为展示的名称,leaf为是否含有子节点,Bool类型。
value,label,leaf 变量名称最好是不要变,如果后端提供的变量名是其他名称,前端拿到之后,自己转一下也行
二、数据回显问题:
在对该条数据点击编辑的时候,选择框内需要展示选中的数据,并且点击展开的选项中,也应该选中该条记录,效果如下:
这里就需要后端提供第二个接口:
2.前端传入指定节点ID,后端返回该节点从顶部节点至下(到该节点)的所有节点ID列表。
格式如下图:
需要特别注意的是顺序!
前端拿到它后,复制给 el-cascader 组件v-model绑定的值即可。
这里的原理就是,前端在新增功能中,提交数据给后端的时候,v-model绑定的参数的值,也是这样的列表形式,所以当绑定值写为这样的列表时,也就能用来回显展示。
而因为后端一般只存最后一个节点(即选中的节点)的数据,我们提交时,也只提交最后一个节点的数据.......或者全部提交,让后端去处理。
所以需要后端开一个接口,查找该接节点之前的各个节点的数据,并放入同一个数组返回,前端再将数据直接写回v-model绑定的参数就行了。
这样就处理完成了,但是有一个问题,应该算是element-ui的BUG,就是只有在首次加载页面后,编辑数据A时,数据A回显一切正常,关闭弹窗,再编辑其他任何数据时,此时弹窗中,el-cascader 组件文本框中,回显都为空。
这个问题,最核心的原因是:
首次编辑数据时,会根据v-model绑定的参数值,
比如["1","2","3","4"] 这个数组
逐层调用lazyLoad获取整个树结构,
即:依次将 "1","2","3","4"作为参数,调用lazyLoad,分别渲染头结点"1",头结点下一层,ID为节点"2"的下一层,以及下一层中,ID为“3”的下一层,最后整个合并起来。
整个过程是组件自动完成的。
所以能正确回显。
但是当关闭编辑弹窗,再编辑其他数据时,虽然el-cascader 组件v-model绑定的参数值发生了变化,但是没有再调用lazyLoad方法再逐层请求后端渲染整个结构树。
所以目前网上的解决办法总体有两种思路:
- 1.给el-cascader组件绑定 options选项,根据v-model绑定参数的值,也就是数组的值,手动调用后端接口,逐个获取下一层的选项,拼接成树结构,将生成树结构给 el-cascader 使用,这样就能正确回显。这种思路其实本质上就是自己写lazyLoad的逻辑,放弃使用官方提供的lazyLoad方法。
- 2.将 el-cascader 组件 重新渲染一次,既然首次加载的时候能正确调用lazyLoad,那每次都重新来,都当成首次加载就好了,这就是用v-if解决的思路。
两种解决思路没有什么优劣之分,本质都是打补丁。希望element-ui官方能修复这个问题,这样我们也不用折腾了。
思路二解决起来更简单,这里使用的也是思路二解决:
<el-cascader
v-if="editCascaderVisible"
v-model="IdList"
:props="props"
></el-cascader>
然后在editRow方法时,手动控制它刷新一次:
editRow(rowData) {
this.editCascaderVisible = false;
// 这里搞个定时器重新载入一下组件就可以触发组件拉取数据
setTimeout(() => {
this.editCascaderVisible = true;
}, 1);
this.IdList= [];
//在编辑的时候等到DOM更新完成再赋值
this.$nextTick(() => {
//将选中数据的值 赋值给表单
this.dataForm = Object.assign({}, rowData);
});
//调用后端接口,获取自顶向下逐个层级ID的列表
this.IdList= this.api_getIdList(rowData.groupId);
//打开修改弹窗
this.dialogVisible = true;
}
这样每次点击编辑的时候,就都能正确回显了。
三、点击单选框选中节点不加载下一级选项
因为我们使用的是lazyLoad模式,点击单选框选中时,不会触发加载下一级选项,如图:
解决办法:
<el-cascader
@change="handleChange"
v-if="editCascaderVisible"
v-model="IdList"
:props="props"
></el-cascader>
//处理单选点击事件
handleChange(e) {
this.$nextTick(() => {
const dom = document.getElementsByClassName("el-radio is-checked");
//这里我把dom打出来看了 最后一个选项才是我选中的节点 即[length-1] 有的博主写的是 第一个元素 即[0] 大家自行尝试
let radioDom = dom[dom.length - 1];
const brother = radioDom.nextElementSibling;
brother.click();
});
},
原理:我们点击标签即label,组件就会触发加载下一级节点,所以当选中的节点发生变化时,我们手动触发点击它的同级下一个元素,也就是label,达到加载下一级节点的目的。
四、点击label标签直接选中
单选的情况下,要选中需要点击radio按钮,非常不方便,需要设置成点击标签页直接选中
<style>
/*单选的级联选择器,点击标签页就可以选中*/
.el-cascader-menu .el-radio {
display: table;
vertical-align: middle;
width: 100%;
height: 100%;
position: absolute;
box-sizing: border-box;
margin-left: -25px;
padding-left: 15px;
margin-top: 6px;
}
.el-cascader-menu .el-radio .el-radio__input {
display: table-cell;
vertical-align: middle;
}
</style>
用css就可以解决,原理是:把单选框的范围放大到整个标签范围,点击标签相当于点单选框。
附:
记录一下解决过程中对 el-cascader 组件的认识:
<el-cascader
ref="editCascader"
v-model="departmentIdList"
:props="propsSearch"
placeholder="请选择业务部门"
style="width: 15vw"
></el-cascader>
加入ref,可以使用
this.$refs.editCascader.panel
获取到 el-cascader 对象
可以使用
this.$refs.editCascader.panel.lazyLoad(cqbankNode);
主动调用lazyLoad绑定的方法,传入的节点要有chidren字段,或者直接取自
let cqbankNode = this.$refs.editCascader.panel.menus[0][0];
menus是菜单项
menus[0]即第一个下拉框列表,
menus[1]即第二个下拉框列表,可以通过修改menus,修改下拉框展示列。
但是根据 menus[0][0] 去获取,有时候节点children为空,可能应该换一个属性去获取,这个思路应该也能做,当时是取值属性来源错了,为空就写不下去了就换思路了。