先看效果图:
github项目地址:地址。
首先,想了解cascader组件的整体结构的,可以先看这篇文章。
我们魔改cascader整个组件的流程如下:
- 添加全选按钮,在页面上先看到。
- 添加全选按钮的点击逻辑。
- 根据cascader-node的集合的check状态去更新全选按钮的状态。
第一步:添加全选按钮
cascader-node组件是在cascader-menu组件中渲染的,渲染代码如下:
renderNodeList(h) {
const { menuId} = this;
const { isHoverMenu } = this.panel;
const events = { on: {} };
if (isHoverMenu) {
events.on.expand = this.handleExpand;
}
// 此处就是要渲染的cascader-node集合
const nodes = this.nodes.map((node, index) => {
const { hasChildren } = node;
return (
<cascader-node
key={ node.uid }
node={ node }
node-id={ `${menuId}-${index}` }
aria-haspopup={ hasChildren }
aria-owns = { hasChildren ? menuId : null }
{ ...events }></cascader-node>
);
});
// 渲染出cascader-node集合,我们只要这里插入全选组件即可。
return [
...nodes,
isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null
];
}
复制代码
添加全选按钮之后的代码如下:
return [
config.checkAll &&
index === 0 &&
<el-checkbox
class="checkAll"
indeterminate={isIndeterminate}
value={checkAll}
onChange={handleCheckAllChange}>全选</el-checkbox>,
...nodes,
isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null
];
复制代码
config.checkAll就是给cascader组件传入的props参数。
比如要我要开启全选功能,那么就要传入checkAll: true的参数。
<el-cascader
:options="options"
:props="props"
collapse-tags
clearable></el-cascader>
data() {
return {
props: { multiple: true, checkAll: true, expandTrigger: 'hover' },
}
}
复制代码
这个全选item就会被渲染出来。
第二步:添加全选按钮的点击逻辑
建议先看看el-checkbox这个组件的’全选‘、’没选‘、’没全选‘这三中状态的逻辑,地址。
再来看一遍cascader-menu的渲染代码块:
return [
config.checkAll &&
index === 0 &&
<el-checkbox
class="checkAll"
indeterminate={isIndeterminate}
value={checkAll}
onChange={handleCheckAllChange}>全选</el-checkbox>,
...nodes,
isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null
];
复制代码
我们给el-checkbox组件添加了两个属性和一个方法:
- value:true->全选,false->待定。
- indeterminate: 当value为true,该属性无效;当value为false,该属性为false则没选,为true则没全选。
- onChange:点击该按钮时触发。
先看onChange的触发函数,请仔细查看代码注释:
handleCheckAllChange() {
const { nodes, panel } = this;
// 反转全选按钮的状态
this.checkAll = !this.checkAll;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
// 触发当前cascader-menu组的所有cascader-node组件的的doCheck方法,效果和背后逻辑与点击cascader-node是一样的。
node.doCheck(this.checkAll, true);
}
// 触发cascader-panel的方法,这方法主要是更新checkValue的值。
panel.calculateMultiCheckedValue();
},
复制代码
我们在cascader-menu这个组件内写一个方法updateInDeterminate,根据cascader-node集合的check状态来更新全选按钮的状态。
updateInDeterminate() {
const { panel, nodes } = this;
if (panel.config.checkAll) {
let counter = 0;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
(node.checked || node.indeterminate) && counter++;
}
this.checkAll = counter === nodes.length;
this.isIndeterminate = !(counter === nodes.length || counter === 0);
}
},
复制代码
至于updateInDeterminate这个方法的触发时机是:
1、cascader-menu创建时
created() {
if (this.panel.config.checkAll && this.index === 0) { this.updateInDeterminate(); this.$on('updateInDeterminate', this.updateInDeterminate); }
},
复制代码
2、cascader-menu中传入的node参数变化时
watch: {
nodes() {
this.panel.config.checkAll && this.index === 0 && this.updateInDeterminate(); }
}
复制代码
3、cascader-panel组件中checkValue(这个指的是cascader选中的值)变化时
watch: {
checkedValue(val) {
if (!isEqual(val, this.value)) {
this.checkStrictly && this.calculateCheckedNodePaths();
// 此处触发
this.broadcast('ElCascaderMenu', 'updateInDeterminate');
this.$emit('input', val);
this.$emit('change', val);
}
}
}
复制代码
4、删除cascader组件的tag时
deleteTag(tag) {
const { checkedValue, panel } = this;
const current = tag.node.getValueByOption();
const val = checkedValue.find(n => isEqual(n, current));
this.checkedValue = checkedValue.filter(n => !isEqual(n, current));
// 如果全选功能开启了,那么删除tag时,就得触发updateInDeterminate函数
if (this.config.checkAll) {
this.$nextTick(() => {
panel.$refs.menu.forEach((menu) => {
menu.updateInDeterminate();
});
});
}
this.$emit('remove-tag', val);
},
复制代码