最近遇到了在级联选择器上添加全选框的需求 ,但是项目使用的是Vue2 + Element UI的架构,而我们都知道Element UI提供的级联选择器el-cascader是不支持全选框的,而我又没有在网上找到适合我项目的实现,索性自己实现一个组件(源码附在文末,需要自取)
实现效果:
主要功能实现:
1. 全选按钮可选
通过属性 showAllNode 控制在多选模式下全选按钮的添加与否(注意,单选模式下没有全选按钮):
javascript
props: {
showAllNode: {
type: Boolean,
default: false
}
},
methods: {
async fetchDictTree() {
let children = this.data.data[0].children || []
// 添加全选节点
this.treeData = this.showAllNode ? [this.allNode, ...children] : children
// 获取所有节点值(一维数组)
this.allNodeValues = this.getAllNodeValues()
// 修改初始化逻辑
if (this.showAllNode) {
// 选中所有节点(包括全选节点)
this.selectedValue = ['0', ...this.allNodeValues]
this.lastSelectedValue = [...this.selectedValue]
} else {
this.selectedValue = []
this.lastSelectedValue = []
}
this.$emit('input', this.selectedValue)
this.$emit('ready', true)
},
}
2. 全选按钮与其他节点的联动效果:
- 点击全选按钮所有节点都被选中,非全选状态下全选按钮不被选中
javascript
if (this.showAllNode) {
// 检查是否包含全选节点
const hasAll = value.includes('0')
const prevHasAll = this.lastSelectedValue.includes('0')
if (hasAll && !prevHasAll) {
// 选中全选,同时选中所有节点
const allValues = ['0', ...this.allNodeValues]
this.selectedValue = allValues
this.lastSelectedValue = [...allValues]
// 只返回 ['0'] 表示全选
this.$emit('input', ['0'])
this.$emit('change', ['0'])
return
} else if (!hasAll && prevHasAll) {
// 取消全选,清空所有选中
setTimeout(() => {
this.selectedValue = []
this.lastSelectedValue = []
this.$emit('input', [])
this.$emit('change', [])
}, 0)
return
}
// 检查是否所有节点都被选中
const allSelected = this.allNodeValues.every(nodeValue =>
value.includes(nodeValue)
)
// 如果所有节点都被选中,但全选节点没有被选中,则添加全选节点
if (allSelected && !hasAll) {
const newValue = ['0', ...value]
this.selectedValue = newValue
this.lastSelectedValue = [...newValue]
// 只返回 ['0'] 表示全选
this.$emit('input', ['0'])
this.$emit('change', ['0'])
return
}
// 如果不是所有节点都被选中,但全选节点被选中了,则移除全选节点
if (!allSelected && hasAll) {
const newValue = value.filter(v => v !== '0')
this.selectedValue = newValue
this.lastSelectedValue = [...newValue]
this.$emit('input', newValue)
this.$emit('change', newValue)
return
}
}
3. 全选按钮选中时传回的节点数据(dictKey)的值为0而非所有节点key值
javascript
watch: {
value: {
handler(val) {
// 如果传入的值是 ['0'],表示选中全选节点
if (Array.isArray(val) && val.length === 1 && val[0] === '0' && this.showAllNode) {
// 内部选中所有节点
this.selectedValue = ['0', ...this.allNodeValues]
} else if (!this.multiple && Array.isArray(val) && val.length === 1) {
// 单选模式下,如果传入的是数组,取第一个元素
this.selectedValue = val[0]
} else {
this.selectedValue = val
}
},
immediate: true
},
},
组件使用:
html
<template>
<div>
<classify-cascader v-model="classify" placeholder="请选择试卷分类" multiple
checkStrictly show-all-node width="200px" />
</div>
</template>
<script>
import classifyCascader from '@/components/classify-cascader/index.vue'
export default {
name: 'indexVue',
components: {
classifyCascader
},
data() {
return {
classify: []
}
}
}
</script>
- 笔者已经分装好了一个组件,可以直接使用,如果下拉框的数据是从后端获取的话,改一下fetchDictTree() 方法中 children 的数据赋值代码就可以,类似于:
javascript
const { data } = await APIgetDictTree(params)
let children = data.data[0].children || []
组件实现:
javascript
<template>
<el-cascader v-model="selectedValue" :options="treeData" :props="cascaderProps" :placeholder="placeholder"
:disabled="disabled" :clearable="clearable" :style="{ width: width }" collapse-tags @change="handleChange" />
</template>
<script>
export default {
name: 'ClassifyCascader',
props: {
value: {
type: [String, Array],
default: () => []
},
checkStrictly: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: false
},
showAllNode: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: '请选择'
},
clearable: {
type: Boolean,
default: true
},
width: {
type: String,
default: '200px'
}
},
data() {
return {
treeData: [],
selectedValue: this.value,
cascaderProps: {
value: 'dictKey',
label: 'dictValue',
children: 'children',
checkStrictly: this.checkStrictly,
multiple: this.multiple,
emitPath: false
},
allNode: {
dictKey: '0',
dictValue: '全选'
},
lastSelectedValue: [],
allNodeValues: [], // 存储所有节点的值(一维数组)
data: {
data: [
{
children: [
{
dictKey: '1',
dictValue: '分类1'
},
{
dictKey: '2',
dictValue: '分类2',
children: [
{
dictKey: '2-1',
dictValue: '子分类2-1',
children: []
},
{
dictKey: '2-2',
dictValue: '子分类2-2',
children: []
}
]
}
]
}
]
}
}
},
watch: {
value: {
handler(val) {
// 如果传入的值是 ['0'],表示选中全选节点
if (Array.isArray(val) && val.length === 1 && val[0] === '0' && this.showAllNode) {
// 内部选中所有节点
this.selectedValue = ['0', ...this.allNodeValues]
} else if (!this.multiple && Array.isArray(val) && val.length === 1) {
// 单选模式下,如果传入的是数组,取第一个元素
this.selectedValue = val[0]
} else {
this.selectedValue = val
}
},
immediate: true
},
},
created() {
this.fetchDictTree()
},
methods: {
// 刷新数据
refreshData() {
return this.fetchDictTree()
},
async fetchDictTree() {
let children = this.data.data[0].children || []
// 添加全选节点
this.treeData = this.showAllNode ? [this.allNode, ...children] : children
// 获取所有节点值(一维数组)
this.allNodeValues = this.getAllNodeValues()
// 修改初始化逻辑
if (this.showAllNode) {
// 选中所有节点(包括全选节点)
this.selectedValue = ['0', ...this.allNodeValues]
this.lastSelectedValue = [...this.selectedValue]
} else {
this.selectedValue = []
this.lastSelectedValue = []
}
this.$emit('input', this.selectedValue)
this.$emit('ready', true)
},
handleChange(value) {
if (this.showAllNode) {
// 检查是否包含全选节点
const hasAll = value.includes('0')
const prevHasAll = this.lastSelectedValue.includes('0')
if (hasAll && !prevHasAll) {
// 选中全选,同时选中所有节点
const allValues = ['0', ...this.allNodeValues]
this.selectedValue = allValues
this.lastSelectedValue = [...allValues]
// 只返回 ['0'] 表示全选
this.$emit('input', ['0'])
this.$emit('change', ['0'])
return
} else if (!hasAll && prevHasAll) {
// 取消全选,清空所有选中
setTimeout(() => {
this.selectedValue = []
this.lastSelectedValue = []
this.$emit('input', [])
this.$emit('change', [])
}, 0)
return
}
// 检查是否所有节点都被选中
const allSelected = this.allNodeValues.every(nodeValue =>
value.includes(nodeValue)
)
// 如果所有节点都被选中,但全选节点没有被选中,则添加全选节点
if (allSelected && !hasAll) {
const newValue = ['0', ...value]
this.selectedValue = newValue
this.lastSelectedValue = [...newValue]
// 只返回 ['0'] 表示全选
this.$emit('input', ['0'])
this.$emit('change', ['0'])
return
}
// 如果不是所有节点都被选中,但全选节点被选中了,则移除全选节点
if (!allSelected && hasAll) {
const newValue = value.filter(v => v !== '0')
this.selectedValue = newValue
this.lastSelectedValue = [...newValue]
this.$emit('input', newValue)
this.$emit('change', newValue)
return
}
}
// 正常情况下的处理
this.lastSelectedValue = [...value]
const outputValue = Array.isArray(value) ? value : [value]
this.$emit('input', outputValue)
this.$emit('change', outputValue)
},
// 获取所有节点的值(一维数组)
getAllNodeValues() {
const allValues = []
const traverse = (nodes) => {
nodes.forEach(node => {
if (node.dictKey === '0') return
allValues.push(node.dictKey)
if (node.children && node.children.length > 0) {
traverse(node.children)
}
})
}
traverse(this.treeData)
return allValues
}
}
}
</script>
大家有什么问题可以评论区交流一下,我看到了也会回复的。