el-table 树形表格复选框联动功能实现指南
我将为您详细介绍在 Element Plus 的 el-table 树形表格中实现复选框联动功能的完整解决方案。
一、基本实现方案
1. 数据结构准备
首先需要一个包含父子关系的数据结构:
// 示例数据
const tableData = [
{
id: 1,
label: '一级 1',
children: [
{
id: 11,
label: '二级 1-1',
children: [
{ id: 111, label: '三级 1-1-1' },
{ id: 112, label: '三级 1-1-2' }
]
}
]
},
{
id: 2,
label: '一级 2',
children: [
{ id: 21, label: '二级 2-1' },
{ id: 22, label: '二级 2-2' }
]
}
]
2. 组件基本配置
<template>
<el-table
ref="tableRef"
:data="tableData"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
@select="handleSelect"
@select-all="handleSelectAll"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" :selectable="checkSelectable" />
<el-table-column prop="label" label="名称" />
<!-- 其他列 -->
</el-table>
</template>
二、完整实现方案
1. 完整组件示例
<template>
<div class="tree-table-container">
<div class="operation-area">
<el-button @click="expandAll">展开全部</el-button>
<el-button @click="collapseAll">折叠全部</el-button>
<el-button @click="checkAll">全选</el-button>
<el-button @click="uncheckAll">取消全选</el-button>
<el-button @click="getCheckedNodes">获取选中节点</el-button>
<span class="selected-info">已选择: {{ selectedCount }} 个节点</span>
</div>
<el-table
ref="treeTableRef"
:data="tableData"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
default-expand-all
:row-class-name="tableRowClassName"
@select="handleSelect"
@select-all="handleSelectAll"
@selection-change="handleSelectionChange"
@expand-change="handleExpandChange"
>
<el-table-column
type="selection"
width="55"
:selectable="checkSelectable"
:reserve-selection="true"
/>
<el-table-column prop="label" label="名称" width="200">
<template #default="{ row }">
<span class="label-content">
{{ row.label }}
<el-tag v-if="row.children && row.children.length" size="small" type="info">
{{ row.children.length }} 个子项
</el-tag>
</span>
</template>
</el-table-column>
<el-table-column prop="id" label="ID" width="100" />
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button
size="small"
@click="toggleSelectNode(row)"
:type="isNodeChecked(row) ? 'danger' : 'primary'"
>
{{ isNodeChecked(row) ? '取消选择' : '选择' }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 选中节点展示 -->
<div class="selected-nodes">
<h3>已选中的节点:</h3>
<el-tag
v-for="node in selectedNodes"
:key="node.id"
closable
@close="removeSelectedNode(node)"
class="selected-tag"
>
{{ node.label }} (ID: {{ node.id }})
</el-tag>
<p v-if="selectedNodes.length === 0">暂无选中节点</p>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
// 表格数据
const tableData = ref([
{
id: 1,
label: '部门A',
children: [
{
id: 11,
label: '部门A-1',
children: [
{ id: 111, label: '员工A-1-1' },
{ id: 112, label: '员工A-1-2' }
]
},
{
id: 12,
label: '部门A-2',
children: [
{ id: 121, label: '员工A-2-1' },
{ id: 122, label: '员工A-2-2' }
]
}
]
},
{
id: 2,
label: '部门B',
children: [
{ id: 21, label: '部门B-1' },
{
id: 22,
label: '部门B-2',
disabled: true // 禁用的节点
}
]
},
{
id: 3,
label: '独立节点',
children: []
}
])
// 表格引用
const treeTableRef = ref()
// 选中节点
const selectedNodes = ref([])
// 计算属性
const selectedCount = computed(() => selectedNodes.value.length)
// 检查节点是否可选
const checkSelectable = (row, index) => {
// 返回 false 表示禁用选择
return !row.disabled
}
// 检查节点是否被选中
const isNodeChecked = (node) => {
return selectedNodes.value.some(item => item.id === node.id)
}
// 处理单个选择
const handleSelect = (selection, row) => {
const isSelected = selection.some(item => item.id === row.id)
if (isSelected) {
// 选中节点及其所有子节点
selectNodeAndChildren(row, true)
// 如果父节点所有子节点都被选中,则选中父节点
updateParentSelection(row)
} else {
// 取消选中节点及其所有子节点
selectNodeAndChildren(row, false)
// 取消选中父节点
updateParentSelection(row)
}
}
// 处理全选
const handleSelectAll = (selection) => {
if (selection.length > 0) {
// 全选所有可见的且可选的节点
selectAllVisibleNodes(true)
} else {
// 取消全选所有节点
selectAllVisibleNodes(false)
}
}
// 选择状态变化
const handleSelectionChange = (selection) => {
selectedNodes.value = [...selection]
}
// 展开/折叠变化
const handleExpandChange = (row, expandedRows) => {
console.log('展开状态变化:', row.label, expandedRows)
}
// 选中节点及其所有子节点
const selectNodeAndChildren = (node, selected) => {
// 如果节点不可选,直接返回
if (node.disabled) return
const treeTable = treeTableRef.value
if (selected) {
// 选中节点
treeTable.toggleRowSelection(node, true)
// 递归选中所有子节点
if (node.children && node.children.length) {
node.children.forEach(child => {
selectNodeAndChildren(child, true)
})
}
} else {
// 取消选中节点
treeTable.toggleRowSelection(node, false)
// 递归取消选中所有子节点
if (node.children && node.children.length) {
node.children.forEach(child => {
selectNodeAndChildren(child, false)
})
}
}
}
// 更新父节点的选择状态
const updateParentSelection = (node) => {
// 这里需要根据实际数据结构查找父节点
// 在实际项目中,你可能需要维护一个父节点引用或使用其他方法查找父节点
// 这里是一个简化的示例
const findAndUpdateParent = (data, childNode) => {
for (const item of data) {
if (item.children && item.children.includes(childNode)) {
const allChildrenSelected = item.children.every(child =>
child.disabled || isNodeChecked(child)
)
const someChildrenSelected = item.children.some(child =>
!child.disabled && isNodeChecked(child)
)
const treeTable = treeTableRef.value
if (allChildrenSelected) {
treeTable.toggleRowSelection(item, true)
} else if (!someChildrenSelected) {
treeTable.toggleRowSelection(item, false)
} else {
// 部分选中状态,表格本身会处理
}
// 继续向上更新
updateParentSelection(item)
return true
}
if (item.children) {
if (findAndUpdateParent(item.children, childNode)) {
return true
}
}
}
return false
}
findAndUpdateParent(tableData.value, node)
}
// 选择所有可见节点
const selectAllVisibleNodes = (selected) => {
const treeTable = treeTableRef.value
// 获取所有行数据
const getAllRows = (data) => {
let rows = []
data.forEach(item => {
if (!item.disabled) {
rows.push(item)
}
if (item.children) {
rows = rows.concat(getAllRows(item.children))
}
})
return rows
}
const allRows = getAllRows(tableData.value)
allRows.forEach(row => {
treeTable.toggleRowSelection(row, selected)
})
}
// 展开全部
const expandAll = () => {
const treeTable = treeTableRef.value
// 获取所有行
const getAllRows = (data) => {
let rows = []
data.forEach(item => {
rows.push(item)
if (item.children) {
rows = rows.concat(getAllRows(item.children))
}
})
return rows
}
getAllRows(tableData.value).forEach(row => {
treeTable.toggleRowExpansion(row, true)
})
}
// 折叠全部
const collapseAll = () => {
const treeTable = treeTableRef.value
// 获取所有行
const getAllRows = (data) => {
let rows = []
data.forEach(item => {
rows.push(item)
if (item.children) {
rows = rows.concat(getAllRows(item.children))
}
})
return rows
}
getAllRows(tableData.value).forEach(row => {
treeTable.toggleRowExpansion(row, false)
})
}
// 全选
const checkAll = () => {
selectAllVisibleNodes(true)
}
// 取消全选
const uncheckAll = () => {
selectAllVisibleNodes(false)
}
// 获取选中节点
const getCheckedNodes = () => {
console.log('当前选中节点:', selectedNodes.value)
return selectedNodes.value
}
// 通过按钮切换选择状态
const toggleSelectNode = (node) => {
const treeTable = treeTableRef.value
const currentlySelected = isNodeChecked(node)
treeTable.toggleRowSelection(node, !currentlySelected)
}
// 移除选中的节点
const removeSelectedNode = (node) => {
const treeTable = treeTableRef.value
treeTable.toggleRowSelection(node, false)
}
// 行类名,用于特殊标记
const tableRowClassName = ({ row }) => {
if (row.disabled) {
return 'disabled-row'
}
return ''
}
// 初始化时设置一些默认选中的节点
onMounted(() => {
// 示例:默认选中ID为111和21的节点
const findNodeById = (data, id) => {
for (const item of data) {
if (item.id === id) {
return item
}
if (item.children) {
const found = findNodeById(item.children, id)
if (found) return found
}
}
return null
}
const node111 = findNodeById(tableData.value, 111)
const node21 = findNodeById(tableData.value, 21)
if (node111) treeTableRef.value.toggleRowSelection(node111, true)
if (node21) treeTableRef.value.toggleRowSelection(node21, true)
})
</script>
<style scoped>
.tree-table-container {
padding: 20px;
}
.operation-area {
margin-bottom: 20px;
}
.operation-area .el-button {
margin-right: 10px;
}
.selected-info {
margin-left: 20px;
color: #409eff;
font-weight: bold;
}
.label-content {
display: flex;
align-items: center;
gap: 8px;
}
.selected-nodes {
margin-top: 30px;
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
background-color: #f5f7fa;
}
.selected-nodes h3 {
margin-top: 0;
margin-bottom: 15px;
color: #303133;
}
.selected-tag {
margin-right: 10px;
margin-bottom: 10px;
}
/* 禁用行的样式 */
:deep(.disabled-row) {
color: #c0c4cc;
cursor: not-allowed;
}
/* 选中行的样式 */
:deep(.el-table__row.selected-row) {
background-color: #f0f9ff;
}
</style>
三、高级功能实现
1. 三种联动模式实现
// 在组件中添加联动模式选项
const linkageMode = ref('parent-child') // 'parent-child', 'child-parent', 'independent'
// 修改 handleSelect 方法以支持不同模式
const handleSelect = (selection, row) => {
if (linkageMode.value === 'parent-child') {
// 父选子模式:选中父节点时自动选中所有子节点
handleParentChildMode(selection, row)
} else if (linkageMode.value === 'child-parent') {
// 子选父模式:选中所有子节点时自动选中父节点
handleChildParentMode(selection, row)
} else {
// 独立模式:互不干扰
handleIndependentMode(selection, row)
}
}
// 父选子模式
const handleParentChildMode = (selection, row) => {
const isSelected = selection.some(item => item.id === row.id)
if (isSelected) {
// 选中父节点及其所有子节点
selectNodeAndChildren(row, true)
} else {
// 取消选中父节点及其所有子节点
selectNodeAndChildren(row, false)
}
}
// 子选父模式
const handleChildParentMode = (selection, row) => {
const isSelected = selection.some(item => item.id === row.id)
const treeTable = treeTableRef.value
// 更新当前节点
treeTable.toggleRowSelection(row, isSelected)
// 向上检查父节点状态
updateParentSelection(row)
}
// 独立模式
const handleIndependentMode = (selection, row) => {
// 不做任何联动处理
console.log('独立模式选择:', row)
}
2. 部分选中状态处理
// 添加部分选中状态管理
const indeterminateNodes = ref(new Set())
// 检查并更新部分选中状态
const updateIndeterminateStatus = (node) => {
if (!node.children || node.children.length === 0) return
const checkedChildren = node.children.filter(child =>
!child.disabled && isNodeChecked(child)
).length
const totalChildren = node.children.filter(child => !child.disabled).length
if (checkedChildren > 0 && checkedChildren < totalChildren) {
// 部分选中
indeterminateNodes.value.add(node.id)
} else {
indeterminateNodes.value.delete(node.id)
}
}
// 在每次选择后更新部分选中状态
const updateAllIndeterminateStatus = (data) => {
data.forEach(item => {
updateIndeterminateStatus(item)
if (item.children) {
updateAllIndeterminateStatus(item.children)
}
})
}
四、常见问题与解决方案
1. 性能优化
- 大数据量时使用懒加载
- 使用防抖处理频繁的选择操作
- 虚拟滚动支持
2. 数据同步问题
- 确保选中状态与数据同步
- 处理异步加载的子节点
- 维护选中节点的唯一标识
3. 权限控制
- 根据权限禁用某些节点
- 只读模式支持
- 部分节点可选择控制
五、最佳实践建议
数据规范化:确保每个节点有唯一的标识符
状态管理:使用 Vuex 或 Pinia 管理选中状态
性能考虑:对于大数据量,考虑分页或虚拟滚动
用户体验:提供清晰的选中反馈和操作指引
错误处理:处理各种边界情况和异常状态
这个完整实现涵盖了树形表格复选框联动的主要功能,您可以根据具体需求进行调整和扩展。