当前位置: 首页 > news >正文

自定义实现 vxe-table 展开子表格的树结构复选框

在实际业务开发中,我们经常遇到主表格嵌套子表格,且需要在主表和子表上都提供复选框功能,并实现三态联动(全选、半选、未选)。本文将基于 Vue 3 + vxe-table 组件库,手把手教你实现一个支持父子表复选框联动的完整示例。

主表每行都有一个复选框,点击可同时控制其下所有子行;子表内部复选框单独控制,并能反向影响父行状态;表头提供全选/全不选按钮,支持全局批量操作

效果

  1. 主表行复选框 → 勾选时,该行下所有子行自动勾选;取消时,子行全部取消。
  2. 子表行复选框 → 勾选/取消时,自动更新所属父行的选中状态(全选/半选/未选)。
  3. 表头全选 → 一键选中/取消所有主行及其所有子行。
  4. 半选状态 → 当某个主行下只有部分子行被选中时,该行复选框显示半选(indeterminate)状态;表头同理。

实现思路

vxe-table 提供了强大的模板插槽能力,我们利用三个插槽来实现自定义复选框:

  • cell_one_checkbox – 主表每行的复选框插槽
  • head_one_checkbox – 表头的全选/半选插槽
  • expand_content – 展开行内容,内部嵌套子表格,子表格的复选框使用 cell_two_checkbox 插槽
    父子联动逻辑通过以下四个核心函数完成:
  • selectAllEvent():表头全选/全不选
  • changeOneSelect(row):主表某行复选框变化
  • changeTwoSelect(childRow, parentRow):子表某行复选框变化
  • checkOneSelectStatus():更新表头的全选/半选状态

代码

<template><div><vxe-gridv-bind="gridOptions"><template#expand_content="{ row }"><divclass="expand-wrapper"><vxe-gridv-bind="childGridOptions":data="row.childList"><template#cell_two_checkbox="{ row: childRow }"><vxe-checkboxv-model="childRow.isChecked":indeterminate="childRow.isHalf"@change="changeTwoSelect(childRow, row)"></vxe-checkbox></template></vxe-grid></div></template><template#head_one_checkbox><vxe-checkboxv-model="isAllChecked":indeterminate="isAllHalf"@change="selectAllEvent"></vxe-checkbox></template><template#cell_one_checkbox="{ row }"><vxe-checkboxv-model="row.isChecked":indeterminate="row.isHalf"@change="changeOneSelect(row)"></vxe-checkbox></template></vxe-grid></div></template><scriptsetup>import{ref,reactive}from'vue'importXEUtilsfrom'xe-utils'constisAllChecked=ref(false)constisAllHalf=ref(false)constgridOptions=reactive({border:true,height:600,rowConfig:{keyField:'id'},columns:[{type:'seq',width:70},{field:'checkbox',width:60,slots:{default:'cell_one_checkbox',header:'head_one_checkbox'}},{type:'expand',width:60,slots:{content:'expand_content'}},{field:'name',title:'Name'},{field:'sex',title:'Sex'},{field:'age',title:'Age'}],data:[{id:10001,name:'Test1',role:'Develop',sex:'Man',age:28,address:'test abc',isChecked:false,isHalf:false,childList:[{id:10011,name:'Test112',role:'Develop',sex:'Man',age:28,address:'test abc',isChecked:false,isHalf:false},{id:10012,name:'Test134 134134134134134134134134',role:'Test',sex:'Women',age:22,address:'Guangzhou',isChecked:false,isHalf:false}]},{id:10002,name:'Test2',role:'Test',sex:'Women',age:22,address:'Guangzhou',isChecked:false,isHalf:false,childList:[{id:10021,name:'Test233 233233233233233',role:'Designer',sex:'Man',age:34,address:'test 234324',isChecked:false,isHalf:false}]},{id:10003,name:'Test3',role:'PM',sex:'Man',age:32,address:'Shanghai',isChecked:false,isHalf:false,childList:[{id:10031,name:'Test366 366 366 366366366366',role:'Test',sex:'Man',age:76,address:'test rtyty',isChecked:false,isHalf:false},{id:10032,name:'Test345',role:'Develop',sex:'Women',age:56,address:'Guangzhou',isChecked:false,isHalf:false},{id:10032,name:'Test361 361 361361361361361361361361361361361361',role:'Test',sex:'Women',age:21,address:'Guangzhou',isChecked:false,isHalf:false},{id:10033,name:'Test367',role:'Develop',sex:'Women',age:28,address:'Guangzhou',isChecked:false,isHalf:false},{id:10034,name:'Test3213',role:'Test',sex:'Man',age:35,address:'Guangzhou',isChecked:false,isHalf:false},{id:10035,name:'Test3214',role:'Develop',sex:'Women',age:49,address:'Guangzhou',isChecked:false,isHalf:false},{id:10036,name:'Test3216',role:'Test',sex:'Man',age:58,address:'Guangzhou',isChecked:false,isHalf:false}]},{id:10004,name:'Test4',role:'Designer',sex:'Women',age:24,address:'Shanghai',isChecked:false,isHalf:false,childList:[{id:10041,name:'Test456456 456456456456456456',role:'Designer',sex:'Man',age:19,address:'test 3444444',isChecked:false,isHalf:false},{id:10042,name:'Test457',role:'Test',sex:'Women',age:29,address:'rtyty sdfsdf',isChecked:false,isHalf:false}]},{id:10005,name:'Test5',role:'PM',sex:'Man',age:62,address:'Guangzhou',isChecked:false,isHalf:false,childList:[{id:10031,name:'Test366 366 366 366366366366',role:'Test',sex:'Man',age:76,address:'test rtyty',isChecked:false,isHalf:false},{id:10032,name:'Test345',role:'Develop',sex:'Women',age:56,address:'Guangzhou',isChecked:false,isHalf:false},{id:10032,name:'Test361 361 361361361361361361361361361361361361',role:'Test',sex:'Women',age:21,address:'Guangzhou',isChecked:false,isHalf:false},{id:10033,name:'Test367',role:'Develop',sex:'Women',age:28,address:'Guangzhou',isChecked:false,isHalf:false},{id:10034,name:'Test3213',role:'Test',sex:'Man',age:35,address:'Guangzhou',isChecked:false,isHalf:false},{id:10035,name:'Test3214',role:'Develop',sex:'Women',age:49,address:'Guangzhou',isChecked:false,isHalf:false},{id:10036,name:'Test3216',role:'Test',sex:'Man',age:58,address:'Guangzhou',isChecked:false,isHalf:false},{id:10037,name:'Test3217',role:'Test',sex:'Man',age:49,address:'Guangzhou',isChecked:false,isHalf:false}]}]})constchildGridOptions=reactive({border:true,height:200,columns:[{field:'checkbox',width:60,slots:{default:'cell_two_checkbox'}},{field:'name',title:'Name'},{field:'role',title:'Role'},{field:'address',title:'Address'}]})constselectAllEvent=()=>{const{data=[]}=gridOptions isAllHalf.value=falseXEUtils.eachTree(data,(row)=>{row.isChecked=isAllChecked.value row.isHalf=false},{children:'childList'})}constchangeOneSelect=(row)=>{row.isHalf=falseXEUtils.eachTree(row.childList,(childRow)=>{childRow.isChecked=row.isChecked},{children:'childList'})checkOneSelectStatus()}constcheckOneSelectStatus=()=>{const{data=[]}=gridOptions isAllChecked.value=XEUtils.every(data,(row)=>row.isChecked)isAllHalf.value=!isAllChecked.value&&XEUtils.some(data,(row)=>row.isChecked||row.isHalf)}constchangeTwoSelect=(childRow,row)=>{const{childList=[]}=row row.isChecked=XEUtils.every(childList,(row)=>row.isChecked)row.isHalf=!isAllChecked.value&&XEUtils.some(childList,(row)=>row.isChecked)checkOneSelectStatus()}</script><stylelang="scss"scoped>.expand-wrapper{padding:16px;}</style>

数据字段设计

每行数据(主行和子行)都需要包含两个字段:

  • isChecked:Boolean,是否选中
  • isHalf:Boolean,是否半选状态(仅用于界面展示,vxe-checkbox 的 indeterminate 属性)

子行通过 childList 数组与父行关联。

数据状态

  • 父行全选:所有子行 isChecked === true
  • 父行半选:不是全选 且 至少有一个子行被选中
  • 父行未选:所有子行 isChecked === false

https://vxetable.cn

http://www.jsqmd.com/news/826572/

相关文章:

  • 集成三相桥驱动的MCU:AiP8F7201电机控制方案解析
  • 去人类中心主义研究引擎:多模态知识图谱与跨学科关联发现
  • 高校实训兼职老师招聘
  • 如何详解 Git 核心功能?
  • 腾讯会议多租户企业部署实战:Webhook鉴权 + 子账号隔离 + 审计日志完整方案
  • K8S环境搭建(单master)
  • FPGA加速Transformer自注意力矩阵乘法的优化实践
  • Flag-Bridge编码:量子纠错技术的创新突破
  • Arm Neoverse CMN-650 MPAM技术解析与配置实践
  • 深入解析浮点数内存存储与IEEE 754标准:从0.1+0.2≠0.3说起
  • RMSNorm:均方根归一化总结
  • 小学生如何高效通过GESP七八级
  • 从0搭建DeepSeek高性价比推理服务(vLLM + TensorRT-LLM双路径实测):1张H20实现QPS 28.7,资源利用率提升至94.3%
  • 为什么3D高斯泼溅像“撒面粉”?揭秘其高效渲染的奥秘
  • C166双栈机制与嵌入式内存优化实践
  • 周末愉快~
  • 年度名场面!黄仁勋逛胡同被投喂豆汁,眉头紧锁。网友:弥补了没有喝过 XX 的遗憾
  • 别再为SSH断线抓狂了!用autossh在Ubuntu/CentOS上搭建稳定隧道(附systemd服务配置)
  • 架构复盘:武汉丝路云如何用高并发架构支撑跨境业务300%增长?
  • 从0到4倍:一次产品冷启动的完整复盘
  • 前台测试想转后台优化?这4个条件缺一不可,否则别折腾
  • Raycast集成ChatGPT插件:无缝AI助手提升macOS工作流效率
  • Swift集成飞书开放平台:feishu-swift SDK架构解析与实战指南
  • 2026年4月评价高的墙布施工团队推荐,木卷帘/办公室墙布/软硬包/遮光卷帘/遮阳卷帘/智能窗帘/天窗,墙布定制厂家推荐 - 品牌推荐师
  • 2026年值得关注的ClaudeAPI加速站榜单:为开发者提供高效、稳定且实惠的AI调用解决方案
  • 嵌入式主板选型指南:X86与ARM架构对比与工业应用实战
  • 硬件预取技术:Alecto框架优化内存访问性能
  • Tattu亮相2026深圳世界无人机大会 聚焦低空经济,共探无人系统产业未来
  • 从EGO-Planner到集群协同:分布式轨迹优化在无人机编队中的应用
  • 核心代码编程-社交网络相同爱好好友查询-200分