龙岩市浔绾网

el-table树形表格中复选框联动功能操作大全

2026-03-25 18:38:02 浏览次数:1
详细信息

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 管理选中状态 性能考虑:对于大数据量,考虑分页或虚拟滚动 用户体验:提供清晰的选中反馈和操作指引 错误处理:处理各种边界情况和异常状态

这个完整实现涵盖了树形表格复选框联动的主要功能,您可以根据具体需求进行调整和扩展。

相关推荐