先上效果图
在最新的 Element UI 或 Element Plus 版本中,
el-cascader
组件不支持通过点击文字选中项的原因,可能涉及几个关键因素:
- 设计简化: 该组件默认的交互是点击复选框或单选框来选择选项,而不是点击文字。这种设计可以减少误选项的可能性,避免在多级菜单中用户不小心点击文字而误选。
- 避免冲突和误操作: 在多级菜单中,点击文字可能导致意外的展开、收起,或选择,尤其是当用户只是想展开子菜单但意外选中了当前项。这种设计选择可能是为了减少误操作,增强对复杂层级结构的控制。
- 性能优化: 增加对文本的点击事件监听会涉及更多的 DOM 操作。每次级联项展开都需要重新绑定事件监听器,频繁的 DOM 操作可能影响性能,特别是在复杂多级数据结构中。
- 一致性: Element UI 和 Element Plus 遵循单向数据流的原则,尤其是在表单组件中。组件交互行为的一致性可以让用户更好地理解和操作,默认只在复选框和单选框上触发点击选择,这种一致性简化了操作体验。
如何实现文字点击选中
尽管默认行为不支持点击文字选中,但可以用 JavaScript 和 DOM 操作来手动实现,如前述代码示例,通过监听标签的
click
事件,找到对应的复选框或单选框并触发点击:
- 在
@expand-change
事件中设置当前层级后,在下一次 DOM 更新后调用setCascaderDomEvent
。 - 在
setCascaderDomEvent
中绑定.el-cascader-node__label
标签的click
事件,调用cascaderCheckEvent
手动触发输入框的点击事件,从而实现文字选中效果。
这样的方法虽然可以达到效果,但可能需要根据新版本中 DOM 结构的变化进行适配。
方法如下
1.
expandChange(event)
expandChange(event) {
this.currentLevel = event.length
this.$nextTick(() => {
this.setCascaderDomEvent()
})
}
- **
event.length
: **expandChange
事件在展开或收起级联菜单时触发,event
参数是一个包含当前展开的级别数组。event.length
就是当前展开的级别数,存储到this.currentLevel
中,以便后续操作。 - this.$nextTick:
this.$nextTick()
是 Vue.js 提供的一个方法,用于在 DOM 更新后执行某个操作。Vue 可能会对 DOM 进行异步更新,$nextTick
可以确保在下一次 DOM 更新循环中执行提供的回调函数。这是因为在展开/收起菜单后,DOM 元素可能需要时间进行渲染,因此延迟调用以确保可以正确访问最新的 DOM 结构。 - setCascaderDomEvent()
;
:在$nextTick
的回调函数中,调用setCascaderDomEvent
方法。这个方法的功能是在级联选择器中为相应的节点绑定点击事件,确保这些节点在 UI 上可交互。由于是在$nextTick
中调用的,因此可以确保在 DOM 已更新的情况下执行这一绑定操作,这样可以避免因节点未渲染完成而导致的事件绑定失败。
2.
setCascaderDomEvent()
setCascaderDomEvent() {
let cascaderDom = document.querySelectorAll(
'.special-cascader .el-cascader-menu__list'
)
if (cascaderDom.length >= this.currentLevel - 1) {
let optionDom = cascaderDom[this.currentLevel]
optionDom.querySelectorAll('.el-cascader-node__label').forEach((label) => {
let nextDom = label.nextElementSibling
label.addEventListener('click', this.cascaderCheckEvent)
if (!nextDom) {
label.style.cursor = 'pointer'
}
})
}
if (this.currentLevel) {
let optionDom = cascaderDom[this.currentLevel - 1]
optionDom.querySelectorAll('.el-cascader-node__label').forEach((label) => {
let nextDom = label.nextElementSibling
label.addEventListener('click', this.cascaderCheckEvent)
if (!nextDom) {
label.style.cursor = 'pointer'
}
})
}
}
document.querySelectorAll('.special-cascader .el-cascader-menu__list')
: 选择当前级联菜单的所有列表项。special-cascader
是通过popper-class
属性设置的类名,用于特定样式或操作。if (cascaderDom.length >= this.currentLevel - 1)
: 确保当前级别的菜单存在。this.currentLevel - 1
用于计算数组索引,因为currentLevel
是基于级别的,但 DOM 数组索引是从 0 开始的。let optionDom = cascaderDom[this.currentLevel]
: 选择当前级别的 DOM 元素。optionDom.querySelectorAll('.el-cascader-node__label')
: 查找当前菜单级别下的所有标签。label.addEventListener('click', this.cascaderCheckEvent)
: 为每个标签添加点击事件,点击标签时将触发cascaderCheckEvent
方法。if (!nextDom)
: 检查标签后是否有下一个兄弟元素。若没有,表示当前标签是最后一个选项,设置鼠标光标为指针,增强用户体验。
3.
cascaderCheckEvent(event)
cascaderCheckEvent(event) {
let brother = event.target.previousElementSibling; // 获取前一个兄弟元素
const input = brother.querySelector('input[type="checkbox"], input[type="radio"]'); // 支持复选框和单选框
if (input) {
input.click(); // 模拟点击复选框
}
const childMenu = event.target.nextElementSibling; // 获取下一个兄弟元素
if (childMenu && childMenu.classList.contains('el-cascader-menu__list')) {
// 这里可以根据需求添加展开/收起的逻辑
}
},
let brother = event.target.previousElementSibling
: 获取当前点击标签的前一个兄弟元素,该元素通常是输入框(复选框或单选框)。const input = brother.querySelector('input[type="checkbox"], input[type="radio"]')
: 查找输入框。支持复选框和单选框。if (input)
: 确保输入框存在。如果存在,则模拟点击输入框,实际操作上选中该项。const childMenu = event.target.nextElementSibling
: 获取当前标签的下一个兄弟元素,如果存在,则可能是子菜单。if (childMenu && childMenu.classList.contains('el-cascader-menu__list'))
: 检查下一个元素是否是子菜单,可以根据需求添加逻辑来展开或收起子菜单(此逻辑在此代码片段中未具体实现)。
完整代码
<template>
<el-cascader
popper-class="special-cascader"
:options="options"
:props="{
checkStrictly: true,
expandTrigger: 'hover'
}"
placeholder="请选择组织"
@change="handleChange"
@expand-change="expandChange">
</el-cascader>
</template>
<script>
export default {
data() {
return {
value: [],
currentLevel: 0,
props: {
multiple: true
},
options: [{
value: 1,
label: '东南',
children: [{
value: 2,
label: '上海',
children: [{
value: 3,
label: '普陀'
},
{
value: 4,
label: '黄埔'
},
{
value: 5,
label: '徐汇'
}
]
}, {
value: 7,
label: '江苏',
children: [{
value: 8,
label: '南京'
},
{
value: 9,
label: '苏州'
},
{
value: 10,
label: '无锡'
}
]
}, {
value: 12,
label: '浙江',
children: [{
value: 13,
label: '杭州'
},
{
value: 14,
label: '宁波'
},
{
value: 15,
label: '嘉兴'
}
]
}]
}, {
value: 17,
label: '西北',
children: [{
value: 18,
label: '陕西',
children: [{
value: 19,
label: '西安'
},
{
value: 20,
label: '延安'
}
]
}, {
value: 21,
label: '新疆维吾尔族自治区',
children: [{
value: 22,
label: '乌鲁木齐'
},
{
value: 23,
label: '克拉玛依'
}
]
}]
}]
};
},
methods: {
handleChange() {
},
expandChange(event) {
this.currentLevel = event.length
let that = this
setTimeout(() => {
that.setCascaderDomEvent()
}, 0)
},
setCascaderDomEvent() {
let cascaderDom = document.querySelectorAll(
'.special-cascader .el-cascader-menu__list'
)
if (cascaderDom.length >= this.currentLevel - 1) {
let optionDom = cascaderDom[this.currentLevel]
optionDom.querySelectorAll('.el-cascader-node__label').forEach((label) => {
let nextDom = label.nextElementSibling
label.addEventListener('click', this.cascaderCheckEvent)
if (!nextDom) {
label.style.cursor = 'pointer'
}
})
}
if (this.currentLevel) {
let optionDom = cascaderDom[this.currentLevel - 1]
optionDom.querySelectorAll('.el-cascader-node__label').forEach((label) => {
let nextDom = label.nextElementSibling
label.addEventListener('click', this.cascaderCheckEvent)
if (!nextDom) {
label.style.cursor = 'pointer'
}
})
}
},
// 子菜单label监听事件方法
cascaderCheckEvent(event) {
// 查找对应的输入框
let brother = event.target.previousElementSibling // 获取前一个兄弟元素
const input = brother.querySelector(
'input[type="checkbox"], input[type="radio"]'
) // 支持复选框和单选框
if (input) {
input.click() // 模拟点击复选框
}
// 如果有子菜单,展开/收起子菜单
const childMenu = event.target.nextElementSibling
if (childMenu && childMenu.classList.contains('el-cascader-menu__list')) {
// 这里可以根据需求添加展开/收起的逻辑
}
},
},
};
</script>
<style>
</style>
版权归原作者 河坤 所有, 如有侵权,请联系我们删除。