尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

el-table的树形表格全选 半选 取消全选问题解决

el-table的树形表格全选 半选 取消全选问题解决
📅 发布时间:2026/7/8 4:11:36
  • 页面加载时序:created → getList → 请求接口 → 拷贝树 + 初始化数据 → 构建父子映射 → nextTick 回显 echoCheckedState(自底向上)
  • 手动点单行时序:selectFun → setRowIsSelect → 同步所有子节点 → 逐层向上更新所有父节点半选状态 → 更新行 / 表头半选样式
  • 表头全选时序:selectAllFun → 判断是否全选 → 遍历所有顶层节点 → 批量触发单行联动同步整树
  • 保存时序:收集所有选中 / 半选 ID → 弹窗确认 → 提交后端

上代码 :

<template> <div class="rolemeu-container p-5"> <div class="rolemenu-box"> <!-- 权限表格 --> <el-table v-loading="loading" ref="product" :data="renderDynamic" border row-key="id" height="calc(100vh - 205px)" size="mini" max-height="500px" style="width: 100%" :row-class-name="rowClassNameFun" :header-row-class-name="headerRowClassName" :header-cell-style="{ background: '#fafafa' }" @select="selectFun" @select-all="selectAllFun" > <!-- 多选框列 --> <el-table-column type="selection" width="55"></el-table-column> <!-- 序号 --> <el-table-column type="index" label="序号" width="50" align="center" /> <el-table-column prop="name" label="菜单名称" align="center" /> <el-table-column prop="type" label="角色类型" align="center" width="200" /> <el-table-column prop="permission" label="权限类型" min-width="100" align="center" /> </el-table> <div style="margin: 10px 0"> <el-button type="primary" @click="saveCheckedIds">保存权限</el-button> </div> </div> </div> </template> <script> export default { data() { return { loading: false, // 表格加载loading menuList: [], // 后端返回完整权限树形原始数据 roleId: this.$route.query.roleId || "", // 当前编辑角色ID checkedIds: [], // 角色已有权限ID集合(回显用) renderDynamic: [], // 页面渲染用树形副本(避免污染原始数据) parentMap: {}, // 节点ID -> 父节点 对象映射,快速向上递归父级 oneProductIsSelect: [], // 存储顶级节点选中状态,用于判断表头全选框 isInitializingSelection: false, // 初始化回显开关:初始化时禁止触发联动逻辑 }; }, methods: { /** * 页面初始化:获取权限树 + 当前角色已分配权限 */ getList() { if (!this.roleId) { this.$message.warning("未获取到角色信息"); return; } this.loading = true; // 并行请求:全量权限树、当前角色已勾选权限 Promise.all([ premissionApi.getPermissionTreeList(), premissionApi.getRoleMenuList(this.roleId), ]) .then(([menuRes, roleRes]) => { this.menuList = Array.isArray(menuRes.data) ? menuRes.data : []; // 提取当前角色所有权限ID,用于回显勾选 this.checkedIds = this.getAllIds(roleRes.data || []); // 深拷贝树形数据,操作副本不污染原始树 this.renderDynamic = this.cloneTree(this.menuList); // 给每个节点初始化isSelect选中标记 this.initData(this.renderDynamic); this.parentMap = {}; // 递归构建父子节点映射表,方便向上查找父级 this.registerParentMap(this.renderDynamic); this.$nextTick(() => { // 开启初始化标识,阻止回显时触发勾选联动逻辑 this.isInitializingSelection = true; // 根据checkedIds回显所有节点勾选状态,自动计算半选父节点 this.echoCheckedState(this.renderDynamic); // 关闭初始化标识,用户手动勾选才会走联动 this.isInitializingSelection = false; }); }) .catch(() => { this.$message.error("权限数据加载失败"); }) .finally(() => { this.loading = false; }); }, /** * 深拷贝树形结构,防止修改页面渲染数据污染原始接口数据 * @param {Array} list 树形数组 * @returns {Array} 全新拷贝树 */ cloneTree(list) { return (Array.isArray(list) ? list : []).map((item) => ({ ...item, children: this.cloneTree(item.children || []), })); }, /** * 递归构建父子映射 parentMap * key: 节点id value: 父节点完整对象 * @param {Array} list 当前层级节点数组 * @param {Object} parent 当前层级的父节点 */ registerParentMap(list, parent) { (Array.isArray(list) ? list : []).forEach((item) => { this.parentMap[item.id] = parent || null; if (item.children && item.children.length) { this.registerParentMap(item.children, item); } }); }, /** * 根据节点ID获取父节点对象 * @param {Object} row 当前行节点 * @returns {Object|null} 父节点 */ getParentNode(row) { if (!row || row.id === undefined || row.id === null) { return null; } return this.parentMap[row.id] || null; }, /** * 递归提取一棵树所有节点ID(回显角色权限用) * @param {Array} tree 角色已有权限树 * @returns {Array} 所有id集合 */ getAllIds(tree) { const ids = []; const loop = (nodes) => { if (!Array.isArray(nodes)) return; nodes.forEach((item) => { ids.push(item.id); loop(item.children); }); }; loop(tree); return ids; }, /** * 初始化树形所有节点选中状态,默认全部未选中 false * @param {Array} data 树形数组 */ initData(data) { data.forEach((item) => { item.isSelect = false; if (item.children && item.children.length) { this.initData(item.children); } }); }, /** * 权限回显核心:根据checkedIds设置节点勾选,自动计算父级半选/全选 * 递归顺序:先子后父(自底向上计算父节点状态) * @param {Array} data 树形数组 */ echoCheckedState(data) { data.forEach((item) => { const children = item.children || []; // 1. 先递归处理所有子节点 if (children.length) { this.echoCheckedState(children); // 判断子节点状态 const allChildrenChecked = children.every((child) => child.isSelect === true); // 全部子节点选中 const hasCheckedChild = children.some((child) => child.isSelect === true || child.isSelect === ""); // 存在任意选中/半选子节点 // 父节点状态判定 if (allChildrenChecked) { item.isSelect = true; // 子全选 → 父全选 } else if (hasCheckedChild) { item.isSelect = ""; // 部分子选中 → 父半选 } else { item.isSelect = this.checkedIds.includes(item.id); // 子全不选,看自身是否被角色勾选 } } else { // 叶子节点:直接根据角色权限ID判断是否选中 item.isSelect = this.checkedIds.includes(item.id); } // 同步table多选框UI状态 this.syncRowSelection(item); }); }, /** * 同步el-table多选框勾选UI * @param {Object} row 节点行数据 */ syncRowSelection(row) { if (!this.$refs.product) return; // 只有isSelect=true才显示勾选,半选""不打勾,靠css样式区分 this.$refs.product.toggleRowSelection(row, row.isSelect === true); }, /** * 单行勾选/取消触发事件 @select * @param {Array} selection 当前已勾选行集合 * @param {Object} row 当前操作行 */ selectFun(selection, row) { // 初始化回显阶段不执行联动逻辑,防止递归死循环 if (this.isInitializingSelection) return; // 执行勾选联动(子全选、父级向上更新半选状态) this.setRowIsSelect(row); }, /** * 核心联动逻辑:勾选单行后,同步所有子节点 + 向上更新所有父节点状态 * @param {Object} row 当前操作行 */ setRowIsSelect(row) { // 如果当前是半选状态,点击直接转为选中 if (row.isSelect === "") { row.isSelect = false; this.$refs.product.toggleRowSelection(row, true); } // 取反当前节点选中状态 true ↔ false row.isSelect = !row.isSelect; const that = this; /** * 递归全选/取消当前节点所有后代子节点 * @param {Array} data 子节点数组 */ function selectAllChildrens(data) { data.forEach((item) => { item.isSelect = row.isSelect; that.$refs.product.toggleRowSelection(item, row.isSelect); if (item.children && item.children.length) { selectAllChildrens(item.children); } }); } /** * 递归收集当前节点下所有后代叶子选中状态 * @param {Array} selectStatusArr 存储状态数组 * @param {Array} data 子节点数组 * @returns {Array} 所有后代isSelect状态集合 */ function getSelectStatus(selectStatusArr, data) { data.forEach((childrenItem) => { selectStatusArr.push(childrenItem.isSelect); if (childrenItem.children && childrenItem.children.length) { getSelectStatus(selectStatusArr, childrenItem.children); } }); return selectStatusArr; } /** * 判断当前节点层级类型,返回层级标识 * 1:顶级父节点(有子节点) * 2:中间父节点(有子节点) * 3:叶子节点(无子节点) * 4:无父级且无子节点(孤立节点) * @param {Object} currentRow 当前操作行 * @returns {Number} 层级标记 */ function getLevelStatus(currentRow) { const parentNode = that.getParentNode(currentRow); if (!parentNode) { if (currentRow.children && currentRow.children.length) return 1; return 4; } if (!currentRow.children || !currentRow.children.length) return 3; return 2; } /** * 向上递归更新所有父节点选中状态(半选/全选/未选) * 操作完子节点后,逐层刷新父级isSelect * @param {Object} currentRow 当前操作节点 */ function operateLastLeve(currentRow) { let selectStatusArr = []; const item = that.getParentNode(currentRow); if (!item || !item.children) return; // 获取当前父节点所有子节点的选中状态 selectStatusArr = getSelectStatus(selectStatusArr, item.children); // 1. 所有子全选 → 父全选 if (selectStatusArr.length && selectStatusArr.every((selectItem) => selectItem === true)) { item.isSelect = true; that.$refs.product.toggleRowSelection(item, true); } // 2. 所有子全不选 → 父取消 else if (selectStatusArr.length && selectStatusArr.every((selectItem) => selectItem === false)) { item.isSelect = false; that.$refs.product.toggleRowSelection(item, false); } // 3. 部分选中 → 父半选 "" else { item.isSelect = ""; that.$refs.product.toggleRowSelection(item, false); } // 存在更上层父节点,继续向上递归更新 if (that.getParentNode(item)) { operateLastLeve(item); } } // 获取当前节点层级类型,执行对应联动逻辑 const levelStatus = getLevelStatus(row); if (levelStatus == 1) { // 顶级父节点:勾选后全选所有子节点 selectAllChildrens(row.children); } else if (levelStatus == 2) { // 中间父节点:全选子节点 + 向上更新所有父级状态 selectAllChildrens(row.children); operateLastLeve(row); } else if (levelStatus == 3) { // 叶子节点:仅向上更新所有父级半选/选中状态 operateLastLeve(row); } }, /** * 判断所有顶级节点是否全部选中,用于表头全选框状态计算 * @returns {Boolean} true=全部顶级选中 false=存在未选/半选 */ checkIsAllSelect() { this.oneProductIsSelect = []; this.renderDynamic.forEach((item) => { this.oneProductIsSelect.push(item.isSelect); }); return this.oneProductIsSelect.every((selectStatusItem) => selectStatusItem === true); }, /** * 表头全选/取消全选事件 @select-all * @param {Array} selection 已勾选行集合 */ selectAllFun(selection) { // 初始化回显阶段不执行 if (this.isInitializingSelection) return; // 判断当前是否已经全部选中 const isAllSelect = this.checkIsAllSelect(); // 遍历所有顶级节点,取反全选状态 this.renderDynamic.forEach((item) => { item.isSelect = isAllSelect; this.$refs.product.toggleRowSelection(item, !isAllSelect); // 递归触发子节点联动 this.selectFun(selection, item); }); }, /** * 行class:半选行添加indeterminate样式,渲染中间横线 * @param {Object} {row} 行对象 * @returns {String} class名 */ rowClassNameFun({ row }) { if (row.isSelect === "") return "indeterminate"; return ""; }, /** * 表头多选框class:存在任意半选节点,表头变为半选样式 * @returns {String} class名 */ headerRowClassName() { const topLevelSelectStatus = []; this.renderDynamic.forEach((item) => { topLevelSelectStatus.push(item.isSelect); }); // 顶级节点包含半选"",表头显示半选样式 if (topLevelSelectStatus.includes("")) return "indeterminate"; return ""; }, /** * 递归收集所有有效权限ID(选中true + 半选""都要提交) * @returns {Array} 待提交权限ID数组 */ getCheckedIds() { const ids = []; const loop = (list) => { list.forEach((item) => { // 半选父节点也要提交ID(后端需要完整权限树) if (item.isSelect === true || item.isSelect === "") { ids.push(item.id); } if (item.children && item.children.length) { loop(item.children); } }); }; loop(this.renderDynamic); return ids; }, /** * 保存权限按钮点击事件 */ saveCheckedIds() { const ids = this.getCheckedIds(); if (!ids.length) { this.$message.warning("请至少勾选一个权限"); return; } this.$confirm("确认保存当前权限配置吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", lockScroll: false, }) .then(() => { // 提交角色ID + 所有选中/半选权限ID return premissionApi.grantRoleMenu({ roleId: this.roleId, permissionIds: ids, }); }) .then(() => { // 更新本地缓存回显ID this.checkedIds = ids; this.$message.success("保存成功"); }) .catch((error) => { if (error === "cancel" || error === "close") { this.$message.info("已取消保存"); return; } this.$message.error("保存失败"); }); }, }, created() { // 页面创建加载权限数据 this.getList(); }, } </script> <style lang="scss" scoped> .rolemenu-box { padding: 10px; background: #fff; border-radius: 4px; height: calc(100vh - 110px); /* 半选样式核心:给indeterminate类的复选框绘制中间横线 */ ::v-deep .indeterminate .el-checkbox__input .el-checkbox__inner { background-color: #409eff !important; border-color: #409eff !important; color: #fff !important; } ::v-deep .indeterminate .el-checkbox__input.is-checked .el-checkbox__inner::after { transform: scale(0.5); } ::v-deep .indeterminate .el-checkbox__input .el-checkbox__inner { background-color: #f2f6fc; border-color: #dcdfe6; } ::v-deep .indeterminate .el-checkbox__input .el-checkbox__inner::after { border-color: #c0c4cc !important; background-color: #c0c4cc; } /* 半选复选框中间横线 */ ::v-deep .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; } ::v-deep .product-show th .el-checkbox__inner { display: none !important; } ::v-deep .product-show .el-checkbox__inner { display: block !important; } ::v-deep .product-show .el-checkbox { display: block !important; } } </style>

相关新闻

  • Hermes 上手指南:用一次故障复盘讲清取舍
  • 计算机毕业设计之基于Visualstudio在线超市管理系统
  • Webpack5性能优化实战:从构建速度到产物体积的全面提效指南

最新新闻

  • Mission Planner:无人机飞行控制与任务规划的完整解决方案
  • Express.js:Node.js 生态里绕不开的 Web 框架
  • 水文数据突变点检测:滑动窗口与箱线图在黄河水沙分析中的 3 种应用
  • NOR Flash存内计算芯片WTM2101:1.8MB阵列如何实现10倍能效提升?
  • Linux环境变量与数组变量学习总结
  • 咨询公司都说能落地 AI,能力到底怎么验证?

日新闻

  • PROPKA 3深度解析:蛋白质pKa预测的实战指南与算法原理
  • 微信小程序 globalData 监听:基于 Object.defineProperty 的 3 种实现方案对比
  • MySQL 8.0 数据清洗实战:3类异常值识别与 UPDATE/DELETE 批量处理

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号