最近接到需求需要级联选择器父子互不关联且
- 点击label可以选中
- 双击选中所有子级
一、探索思路
- 定制性这么强的需求第一步当然是使用自定义节点内容
- 文档中没有说明对应功能的函数,只能通过查看源码寻找解决方案
源码中并没有el-tree中的setChecked方法,但是在cascader文件570行发现了handleSuggestionKeyDown函数
该函数中通过触发click事件完成选择操作
到这里大致思路就清晰了,可以使用自定义内容和操作DOM完成以上需求
二、实现方案
1.自定义节点内容
<el-cascader
v-model="value"
v-bind="$attrs"
popper-class="z-cascader"
@change="$emit('change', value)"
>
<template slot-scope="{ node }">
<div
class="z-cascader-event"
@click.stop="handleChoice($event)"
@dblclick.stop="handleMoreChoice(node)"
></div>
<span :id="node.value" class="z-cascader-label">{{ node.label }}</span>
</template>
</el-cascader>
- 操作DOM
methods: {
//单击
handleChoice(e) {
clearTimeout(this.clickSetTimeout);
this.clickSetTimeout = setTimeout(() => {
e.target.parentElement.previousElementSibling.click();
}, 300);
},
//双击
handleMoreChoice(node) {
clearTimeout(this.clickSetTimeout);
const nodeList = getNodeChildren(node);
const value = JSON.parse(JSON.stringify(this.value));
//取消选中
if (node.checked) {
const cancel = [];
nodeList.forEach((item) => {
for (let index = 0; index < value.length; index++) {
if (String(item.path) === String(value[index])) {
cancel.push(item.value);
value.splice(index, 1);
break;
}
}
});
cancel.forEach((id) => {
document.getElementById(id).parentElement.previousElementSibling.click();
});
} else {
//选中
value.forEach((item) => {
for (let index = 0; index < nodeList.length; index++) {
if (String(item) === String(nodeList[index].path)) {
nodeList.splice(index, 1);
break;
}
}
});
nodeList.forEach(({ value }) => {
document.getElementById(value).parentElement.previousElementSibling.click();
});
}
//获取所有子级
function getNodeChildren(data) {
const children = [];
_getNodeChildren(data);
return children;
function _getNodeChildren(_data) {
children.push({ path: _data.path, value: _data.value });
if (_data.children.length) {
for (let index = 0; index < _data.children.length; index++) {
_getNodeChildren(_data.children[index]);
}
}
}
}
},
}
三、完整代码
初版方案,请结合实际情况修改优化使用,欢迎评论区留言讨论。
//由于占用label点击事件故次级菜单的展开方式只能使用hover expandTrigger:'hover' //只支持严格的遵守父子节点不互相关联的情况下使用 checkStrictly:true
<template>
<el-cascader
v-model="value"
v-bind="$attrs"
popper-class="z-cascader"
@change="$emit('change', value)"
>
<template slot-scope="{ node }">
<div
class="z-cascader-event"
@click.stop="handleChoice($event)"
@dblclick.stop="handleMoreChoice(node)"
></div>
<span :id="node.value" class="z-cascader-label">{{ node.label }}</span>
</template>
</el-cascader>
</template>
<script>
export default {
inheritAttrs: false,
model: {
prop: "v",
event: "change",
},
props: {
v: {
type: [Array || String],
default: "",
},
},
data() {
return {
value: "",
clickSetTimeout: null,
};
},
created() {
console.log(this.$attrs);
this.value = this.v;
},
methods: {
handleChoice(e) {
clearTimeout(this.clickSetTimeout);
this.clickSetTimeout = setTimeout(() => {
e.target.parentElement.previousElementSibling.click();
}, 300);
},
handleMoreChoice(node) {
clearTimeout(this.clickSetTimeout);
const nodeList = getNodeChildren(node);
const value = JSON.parse(JSON.stringify(this.value));
if (node.checked) {
const cancel = [];
nodeList.forEach((item) => {
for (let index = 0; index < value.length; index++) {
if (String(item.path) === String(value[index])) {
cancel.push(item.value);
value.splice(index, 1);
break;
}
}
});
cancel.forEach((id) => {
document.getElementById(id).parentElement.previousElementSibling.click();
});
} else {
value.forEach((item) => {
for (let index = 0; index < nodeList.length; index++) {
if (String(item) === String(nodeList[index].path)) {
nodeList.splice(index, 1);
break;
}
}
});
nodeList.forEach(({ value }) => {
document.getElementById(value).parentElement.previousElementSibling.click();
});
}
function getNodeChildren(data) {
const children = [];
_getNodeChildren(data);
return children;
function _getNodeChildren(_data) {
children.push({ path: _data.path, value: _data.value });
if (_data.children.length) {
for (let index = 0; index < _data.children.length; index++) {
_getNodeChildren(_data.children[index]);
}
}
}
}
},
},
};
</script>
<style>
.z-cascader .el-cascader-node {
height: auto;
line-height: 16px;
padding: 8px 30px 8px 20px;
}
.z-cascader .el-checkbox{
pointer-events: none;
}
.z-cascader .z-cascader-event {
width: 100%;
height: 20px;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}
.z-cascader .z-cascader-label {
display: inline-block;
width: 100%;
max-width: 200px;
white-space: pre-wrap;
word-break: break-all;
}
</style>