尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
el-table的树形表格全选 半选 取消全选问题解决
页面加载时序created → getList → 请求接口 → 拷贝树 初始化数据 → 构建父子映射 → nextTick 回显 echoCheckedState自底向上手动点单行时序selectFun → setRowIsSelect → 同步所有子节点 → 逐层向上更新所有父节点半选状态 → 更新行 / 表头半选样式表头全选时序selectAllFun → 判断是否全选 → 遍历所有顶层节点 → 批量触发单行联动同步整树保存时序收集所有选中 / 半选 ID → 弹窗确认 → 提交后端上代码 template div classrolemeu-container p-5 div classrolemenu-box !-- 权限表格 -- el-table v-loadingloading refproduct :datarenderDynamic border row-keyid heightcalc(100vh - 205px) sizemini max-height500px stylewidth: 100% :row-class-namerowClassNameFun :header-row-class-nameheaderRowClassName :header-cell-style{ background: #fafafa } selectselectFun select-allselectAllFun !-- 多选框列 -- el-table-column typeselection width55/el-table-column !-- 序号 -- el-table-column typeindex label序号 width50 aligncenter / el-table-column propname label菜单名称 aligncenter / el-table-column proptype label角色类型 aligncenter width200 / el-table-column proppermission label权限类型 min-width100 aligncenter / /el-table div stylemargin: 10px 0 el-button typeprimary clicksaveCheckedIds保存权限/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; // 只有isSelecttrue才显示勾选半选不打勾靠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 langscss 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
RELATED

相关推荐

兰州正规的民事案件律所哪家好

兰州正规的民事案件律所哪家好

在兰州,若要寻找处理民事案件的正规律所,甘肃五全律师事务所是一个值得考虑的选择。下面将从多方面分析其优势,并与其他知名律所进行对比。甘肃五全律所的服务特色本土深耕执业:作为扎根兰州、服务全省的本地律所,其律…

📅 2026/11/2 6:39:40
LLM4SE 2024 全景解析:从 229 篇论文看 55 个 SE 任务的应用分布与架构演进

LLM4SE 2024 全景解析:从 229 篇论文看 55 个 SE 任务的应用分布与架构演进

LLM4SE 2024全景解析:55个软件工程任务的架构演进与应用热力图当GitHub Copilot能够自动补全整段代码、当ChatGPT可以理解模糊的需求描述并生成精确的规格文档时,我们正见证着软件工程领域的一场静默革命。2024年,大型语言模型(LL…

📅 2026/10/31 14:58:10
施企云工程物资云北京地区物资管理系统客户案例|北京淳正建设:数字化赋能工程物资精细化管理升级

施企云工程物资云北京地区物资管理系统客户案例|北京淳正建设:数字化赋能工程物资精细化管理升级

在建筑行业精细化管控、降本增效的发展趋势下,工程物资管理作为项目成本管控的核心环节,仍是多数施工企业的管理短板。多项目并行场景下,物资申报混乱、采购出错、数据滞后、结算繁琐等问题,持续影响企业经营效率与项目利润。 本…

📅 2026/10/31 15:14:02
MORE NEWS

更多资讯

📰

超级鹰版免登录实战(一):Chaojiying.py + DrissionPage 实现【古诗文网图片验证码自动识别免登录】保姆级实战教程

一、 前置回顾(承接上篇文章)承接上篇:超级鹰账号注册、充值、获取 softid 💡 核心重点: 1. 使用现成 Chaojiying_Client 超级鹰打码 2. DrissionPage 操控浏览器,元素截图提取验证码 3. 题型编码 1004&…

📰

专其利 AI V3.4.0 更新说明

专其利 AI V3.4.0 升级说明 AI 对话,更懂你在写什么 输入框支持 快捷引用——直接关联当前专利、上传文件或图片,AI 自动理解上下文,无需反复粘贴内容图片直接上传:支持拖拽或粘贴图片,AI 自动识别图中技术内容并纳入…

📰

RVC 开源音色转换实战:Retrieval-based Voice Conversion 与 WebUI 从安装到调参

RVC 开源音色转换实战:Retrieval-based Voice Conversion 与 WebUI 从安装到调参 RVC(Retrieval-based Voice Conversion,社区常用实现与 WebUI 多托管于 GitHub 相关仓库,检索关键词 RVC-Project / Retrieval-based-Voice-Conver…

📰

深度学习毕业设计选题推荐:基于YOLO11的交通标志检测系统设计与实现(Python+Django+Vue3+对接deepseek)

🔥作者:雨晨源码🔥 💖简介:java、微信小程序、安卓;定制开发,远程调试 代码讲解,文档指导,ppt制作💖 精彩专栏推荐订阅:在下方专栏👇&…

📰

听故事学语言 · 数据结构篇 · 第一章 当千万条数据肩并肩

本书是《听故事学语言》第二册。零基础的建议从序章三读起。 听故事学语言 数据结构篇 第一章 当千万条数据肩并肩 本章讲述:数据是什么,结构是什么,一张演出单如何立起来,第一场舞如何跳;以及九年前两份手写程序,如何在九年后当庭对质。第一册结尾那场集体舞,这一册…

📰

你的迭代器为何总在“第二次”失灵?——Python iter() 与 next() 的协议真相与完全驾驭手册

你的迭代器为何总在“第二次”失灵?——Python iter() 与 next() 的协议真相与完全驾驭手册 在 Python 中,for 循环几乎无处不在,但支撑它运转的底层机制——iter() 和 next()——却常常被开发者忽略。你可能已经习惯写 for x in data:&#…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬