背景
在开发业务时,遇到多级树形的表格的勾选问题,发现ElementUI的table组件没有这个功能,可能是没用对,反正没找到,最后找到篇文章提供了个思路,实现了多级树形结构的双向全选、半选和取消功能。
效果如下:
思路来源:https://www.cnblogs.com/Fancy1486450630/p/16706914.html
大致思路
大致思路就是:
- 初始化格式数据,递归给每一层数据内添加一个标识当前行的选中状态的字段(
isSelect
,用三个值区分:true
为全勾选,false
为未勾选,''
为半选)。添加一个parentId
记录父级的id值。 - 实现父级向下联动勾选或取消勾选,通过递归
children
去改变每层的标记值,同时调用table组件的apitoggleRowSelection
去改变选中状态。 - 实现子级向上操作父级联动勾选、半选或取消勾选,通过初始化的
parentId
去遍历数据数组,去找到父级数组,然后通过递归获取所以子级的选中态集合,如果都为true
则为全选,设置为全选,如果都为false
则为取消选中,否则就为半选,把标记置为空字符串。 - 半选状态的展示,通过table的api属性给每行和表头添加类名,通过补充CSS样式实现半选效果。
代码示例
<template>
<el-table
:data="tableData"
ref="multipleTable"
border
row-key="id"
:row-class-name="getRowClassName"
:header-row-class-name="getHeaderRowClassName"
size="mini"
max-height="500px"
style="width: 100%"
@select="selectFun"
@select-all="selectAllFun"
@selection-change="selectionChangeFun"
:header-cell-style="{ background: '#fafafa' }"
>
<el-table-column type="selection" width="55"> </el-table-column>
<el-table-column prop="date" label="日期" sortable width="180">
</el-table-column>
<el-table-column prop="name" label="姓名" sortable width="180">
</el-table-column>
<el-table-column prop="address" label="地址"> </el-table-column>
</el-table>
</template>
<script>
export default {
name: 'listSelectDemo',
data () {
return {
// 表格数据
tableData: [
{
id: 1,
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
},
{
id: 2,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
children: [
{
id: 3,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
children: [
{
id: 4,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
},
{
id: 5,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}
]
},
{
id: 6,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}
]
}
],
// 勾选中的行数据
selectedData: []
}
},
mounted () {
this.initData(this.tableData)
},
methods: {
// 初始化数据
initData (data, parentId) {
data && data.length && data.forEach((item) => {
// isSelect状态:true为选中状态,false为未选中状态 ,空字符串为未知状态
item.isSelect = false // 默认为不选中
parentId && (item.parentId = parentId)
if (item.children && item.children.length) {
this.initData(item.children, item.id)
}
})
},
// 监听选中事件
selectionChangeFun (selection) {
this.selectedData = selection
},
// 复选框点击事件
selectFun (selection, row) {
this.setRowIsSelect(row)
},
// 复选框点击事件
setRowIsSelect (row) {
// 为空字符串则对应行半选态,点击则应该变为全选,则为true
if (row.isSelect === '') {
this.$refs.multipleTable.toggleRowSelection(row, true)
}
row.isSelect = !row.isSelect
const _this = this
// 判断操作的是子级点复选框还是父级点复选框,如果是父级点,则控制子级点的全选和不全选
// 1、只是父级 2、既是子集,又是父级 3、只是子级
const levelStatus = getCurrentLevel(row)
if (levelStatus === 1) {
// 操作的是父级,则所有子集同步父级状态
selectAllChildren(row.children)
} else if (levelStatus === 2) {
selectAllChildren(row.children)
operateLastLevel(row)
} else if (levelStatus === 3) {
operateLastLevel(row)
}
// 判断当前操作行的层级状态
function getCurrentLevel (row) {
// 1、只是父级 2、既是子集,又是父级 3、只是子级
if (row.parentId === undefined) {
return 1
} else if (row.parentId && !(row.children && row.children.length)) {
return 3
} else {
return 2
}
}
// 递归处理children子级数据的isSelect状态以及对选框状态
function selectAllChildren (children) {
children && children.length && children.forEach((item) => {
item.isSelect = row.isSelect
_this.toggleRowSelection(item, row.isSelect)
if (item.children && item.children.length) {
selectAllChildren(item.children)
}
})
}
// 子级向上操作父级状态
function operateLastLevel (row) {
// 操作的是子节点,则
// 1、获取父节点
// 2、判断子节点选中个数,如果全部选中则父节点设为选中状态,如果都不选中,则为不选中状态,如果部分选择,则设为不明确状态
const item = getExplicitNode(_this.tableData, row.parentId)
const selectStatusArr = getSelectStatus([], item.children)
// 判断所有子集选中态,都为true则父级也选中,都为false则不选中,否则为半选
const allSelected = selectStatusArr.every(selectItem => selectItem === true)
const allUnselected = selectStatusArr.every(selectItem => selectItem === false)
if (allSelected) {
item.isSelect = true
_this.toggleRowSelection(item, true)
} else if (allUnselected) {
item.isSelect = false
_this.toggleRowSelection(item, false)
} else {
item.isSelect = ''
}
// 如果当前节点的parentId存在,则还有父级,继续递归往上一层操作
if (item.parentId !== undefined) {
operateLastLevel(item)
}
}
// 递归获取所有子级的选中状态数组
function getSelectStatus (selectStatusArr, children) {
if (!children || !children.length) {
return selectStatusArr
}
children.forEach((child) => {
selectStatusArr.push(child.isSelect)
if (child.children && child.children.length) {
getSelectStatus(selectStatusArr, child.children)
}
})
return selectStatusArr
}
// 获取明确父级的节点
function getExplicitNode (tableData, parentId) {
let result = null
function findNode (data) {
for (let i = 0; i < data.length; i++) {
if (data[i].id === parentId) {
result = data[i]
break
}
if (data[i].children && data[i].children.length) {
findNode(data[i].children)
}
}
}
findNode(tableData)
return result
}
},
// 表格全选事件
selectAllFun (selection) {
const firstLevelStatus = this.tableData.map(item => item.isSelect)
// 判断一级是否是全选.如果一级产品全为true,则设置为取消全选,否则全选
const isAllSelect = firstLevelStatus.every(status => status)
// 设置所以一级和二级为选中状态
this.tableData.forEach((item) => {
item.isSelect = isAllSelect
this.toggleRowSelection(item, !isAllSelect)
this.selectFun(selection, item)
})
},
// 设置当前行的选中态
toggleRowSelection (row, flag) {
if (row) {
this.$nextTick(() => {
this.$refs.multipleTable &&
this.$refs.multipleTable.toggleRowSelection(row, flag)
})
}
},
// 表格行样式 当当前行的状态为不明确状态时,添加样式,使其复选框为不明确状态样式
getRowClassName ({ row }) {
if (row.isSelect === '') {
return 'indeterminate'
}
},
// 表格标题样式 当一级目录有为不明确状态时,添加样式,使其全选复选框为不明确状态样式
getHeaderRowClassName ({ row }) {
const isIndeterminate = this.tableData.some(item => item.isSelect === '')
if (isIndeterminate) {
return 'indeterminate'
}
return ''
}
}
}
</script>
<style lang="scss">
.indeterminate .el-checkbox__input .el-checkbox__inner {
background-color: #409eff !important;
border-color: #409eff !important;
color: #fff !important;
}
.indeterminate .el-checkbox__input.is-checked .el-checkbox__inner::after {
transform: scale(0.5);
}
.indeterminate .el-checkbox__input .el-checkbox__inner {
background-color: #f2f6fc;
border-color: #dcdfe6;
}
.indeterminate .el-checkbox__input .el-checkbox__inner::after {
border-color: #c0c4cc !important;
background-color: #c0c4cc;
}
.indeterminate .el-checkbox__input .el-checkbox__inner::after {
content: '';
position: absolute;
display: block;
background-color: #fff;
height: 2px;
transform: scale(0.5);
left: 0;
right: 0;
top: 5px;
width: auto !important;
}
</style>
总结
实现内容思路大致与参考博文一致,在原文基础上修改为不需要手动设置parentId,自动格式化生成,以及简化层级判断等各个函数的代码逻辑,优化代码效率,实现效果还算不错,仍然有一些问题,通过table组件的selection-change
事件去获取当前选中数组时,会多次触发。使用建议是提取一个公共的mixin
来导入到需要的表格组件使用。
有更好的解决方案希望大家在评论区留言指正。