先看效果图:
对element-ui的组件二次改造、新增功能有三种方式,请各位务必、务必、务必看这篇文章先
我对element-ui(后面用eui简称)项目进行了改造,然后是一个新的eui,姑且称之为aui吧!
这个aui只是在eui的基础上,给cascader新增了很多功能,比如这篇文章要说的cascader全选功能。
aui的github项目地址:地址。
各位可以点击链接,直接体验aui。
首先,想了解cascader组件的整体结构的,可以先看这篇文章。
我们魔改cascader整个组件的流程如下:
- 添加全选按钮,至少得在页面上先看到这个按钮
- 添加全选按钮的点击逻辑
- 根据cascader-node的集合的check状态去更新全选按钮的状态
第一步:添加全选按钮
cascader-node组件是在cascader-menu组件中渲染的,渲染函数在cascader-menu.vue中
,代码如下:
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
];
}
添加全选按钮之后的代码如下:
renderNodeList(h) {
const { menuId, nodes, checkAll, isIndeterminate, handleCheckAllChange, isCheckAll } = this;
const { isHoverMenu, config } = this.panel;
const events = { on: {} };
if (isHoverMenu) {
events.on.expand = this.handleExpand;
}
const nodeItems = 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>
);
});
return [
isCheckAll &&
<el-checkbox
class="checkAll"
indeterminate={isIndeterminate}
value={checkAll}
onChange={handleCheckAllChange}
>
全选
</el-checkbox>,
[...nodeItems],
isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null
];
}
修改完上面是会报错的,还得执行下面的步骤。
首先,我们得给data定义两个变量:checkAll
和isIndeterminate
,这两个变量一起控制全选按钮的展示,后面会介绍。
data() {
return {
activeNode: null,
hoverTimer: null,
id: generateId(),
// 添加以下两个
checkAll: false,
isIndeterminate: false
};
},
然后我们定义一个计算属性isCheckAll
,来控制全选按钮是否展示
computed: {
isCheckAll() {
let config = this.panel.config;
// 父子关联的话,只有第一个cascader-menu有全选按钮
// 父子不关联的话,所有的cascader-menu都有全选按钮
return config.checkAll && (config.checkStrictly || (!config.checkStrictly && this.index === 0));
}
}
config.checkAll就是给cascader组件传入的props参数。
比如要我要开启全选功能,那么就要传入checkAll: true的参数。
<el-cascader
:options="options"
:props="props"
collapse-tags
clearable>
</el-cascader>
<script>
data() {
return {
props: { multiple: true, checkAll: true, expandTrigger: 'hover' },
}
}
</script>
然后,还是会报错,因为handleCheckAllChange还没写。
第二步:添加全选按钮的点击逻辑
建议先看看el-checkbox这个组件的’全选‘、’没选‘、’没全选‘这三种状态的逻辑,地址。
再来看一遍cascader-menu的渲染代码块:
return [
isCheckAll &&
<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是一样的。
if (!node.isDisabled) node.doCheck(this.checkAll, true);
}
// 触发cascader-panel的方法,这方法主要是更新checkValue的值。
panel.calculateMultiCheckedValue();
},
通过上面的代码,只要用户点击了全选按钮,就能将该cascader-menu里的所有的cascader-node的状态更新。
到这里,全选按钮就能被渲染出来了,同时,点击全选按钮即可选中全部的cascader-node。
第三步:根据cascader-node的集合的check状态去更新全选按钮的状态
接下来有一个问题:
如果我此刻取消了东南,那么全选按钮是不是要切换为非全选呢?
这就需要根据cascader-menu的所有cascader-node的check状态来更新全选按钮的状态。
我们在cascader-menu.vue
文件中写一个方法updateInDeterminate,根据cascader-node集合的check状态来更新全选按钮的状态。
updateInDeterminate() {
const { panel, nodes } = this;
if (panel.config.checkAll) {
// 记录checked:true的数量
let counter = 0;
// 记录disabled:true的数量,就是不可以点击的cascader-node
let disabledCounter = 0;
// 记录非全选状态的cascader-node的数据量
let indeterminateCounter = 0;
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (!node.isDisabled) {
node.checked && counter++;
node.indeterminate && indeterminateCounter++;
} else {
disabledCounter++;
}
}
// 全选按钮的状态有下面两个计算出来
this.checkAll = counter === (this.nodes.length - disabledCounter) && counter > 0;
this.isIndeterminate = this.checkAll ? false : indeterminateCounter > 0 || counter > 0;
}
},
而updateInDeterminate这个方法的触发时机是:
- cascader-menu创建时,在cascader-menu.vue中做修改
created() {
if (this.panel.config.checkAll && this.index === 0) {
this.updateInDeterminate();
this.$on('updateInDeterminate', this.updateInDeterminate);
}
},
- cascader-menu中传入的node参数变化时,
在cascader-menu.vue中做修改
watch: {
nodes() {
this.isCheckAll && this.updateInDeterminate();
}
}
- cascader-panel组件中checkValue(这个指的是cascader选中的值)变化时,
在cascader-panel中做修改
watch: {
checkedValue(val) {
if (!isEqual(val, this.value)) {
this.checkStrictly && this.calculateCheckedNodePaths();
// 此处触发
this.broadcast('ElCascaderMenu', 'updateInDeterminate');
this.$emit('input', val);
this.$emit('change', val);
}
}
}
此处会报错,因为this.broadcast使用了eui自己写的事件触发方法,所以我们得在cascader-panel.vue
文件中引入eui的emitter,并将emitter混入进去。
import emitter from 'element-ui/src/mixins/emitter';
export default {
name: 'ElCascaderPanel',
components: {
CascaderMenu
},
mixins: [emitter],
还要在cascader-menu.vue
中也干相同的操作
import emitter from 'element-ui/src/mixins/emitter';
export default {
name: 'ElCascaderMenu',
// 这里一定要添加这行
componentName: 'ElCascaderMenu',
mixins: [Locale, emitter]
- 删除cascader组件的tag时,
在cascader.vue文件中修改
deleteTag(tag) {
// 此处添加panel
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);
},
这样我们就能得到一下的结果: