发布于2026-07-23 阅读(0)
扫一扫,手机访问

先说清楚两个核心需求点,这决定了后续实现的逻辑走向。
第一,父级复选框要能控制所有子级复选框。选中父级,子级全部跟着选中;取消父级,子级也全部取消。这个逻辑很直观,也好理解。
第二,反过来,子级复选框的状态变化不影响父级。也就是说,就算所有子级都选中了,父级也不会自动勾上;父级全选后,哪怕你把所有子级都取消勾选,父级的状态依然保持原样。这跟常见的父子联动逻辑不太一样,需要特别处理。
核心思路是给table列表数据加一个自定义标识(isCheck = false),通过复选框的点击事件来改变这个标识,再根据标识去控制复选框的实际选中状态。
先看基础代码结构,表格的配置没什么特别之处,关键点在于 @select 和 @select-all 两个事件。
1. 初始化表格数据,给每个节点都加上 isCheck 标识,默认为 false。
initCheckedBox() { const initList = function(data) { data.forEach(function(item) { item.isCheck = false // 未选中 if (item.children) { initList(item.children) } }) return data } this.rightCheckList = initList(this.tableList) },
2. 先看全选逻辑。当用户点击表头全选复选框时触发的事件。
// 当用户手动勾选全选 Checkbox 时触发的事件 selectAll(selection) { if (!this.checkAll) { this.checkAll = true this.isAllChecked(this.rightCheckList, true) } else { this.checkAll = false this.isAllChecked(this.rightCheckList, false) } const selectCheck = this.$getNodesIterative(this.rightCheckList, 'children', 'isCheck', true) this.selectedRows = selectCheck.map(item => item.id) }, // 是否全选中 isAllChecked(data, status) { if (data === undefined) { const imitData = [] imitData.push(data) } else { data.forEach(item => { item.isCheck = item.parentId === 0 ? false : status this.$refs.treeTable.toggleRowSelection(item, item.isCheck) if (item.children) { this.isAllChecked(item.children, status) } }) } },
注意:
checkAll默认值为 false。
3. 再来看行级别的选择逻辑。这部分的处理要复杂一些,因为要根据节点的层级关系做不同操作。
handleSelection(selection, row) { row.isCheck = !row.isCheck if(row.children.length === 0 && row.isCheck === true){ this.checkedParentRow(row, this.rightCheckList) } else if(row.children.length === 0 && row.isCheck === false){ this.clearParentRow(row, this.rightCheckList) } else if(row.children.length > 0 && row.isCheck === true){ this.isAllChecked(row.children, true) } else if(row.children.length > 0 && row.isCheck === false){ this.isAllChecked(row.children, false) } const selectCheck = this.$getNodesIterative(this.rightCheckList, 'children', 'isCheck', true) this.selectedRows = selectCheck.map(item => item.id) }, // 选中子节点默认选中父节点,暂时不用,这段与需求不同,可不写。 // 如果有这个需求可写,并取消if隐藏代码 checkedParentRow(data, obj) { for(var item in obj){ if(obj[item].id === data.parentId){ var every = obj[item].children.every(function(item) { return item.isCheck === true }) // if (every) { // obj[item].isCheck = true // this.$refs.treeTable.toggleRowSelection(obj[item], true) // } } } }, // 子节点都未被选中,父节点默认取消选中 clearParentRow(data, obj) { const parentRow = this.$findTreeObjById(obj, 'id', data.parentId) parentRow.isCheck = false this.$refs.treeTable.toggleRowSelection(parentRow, false) },
其中
this.$getNodesIterative和this.$findTreeObjById被封装成了全局方法,具体实现如下:
/** * * @param {Array} treeList 树形数据 * @param {string} targetType 树形数据中所查找的键值 * @param {string} target 树形数据中所查找的值 * @returns */export function findTreeObjById(treeList, targetType, target) { for (const item of treeList) { // 匹配当前节点 if (item[targetType] === target) return item; // 递归查找子节点 if (item.children && item.children.length > 0) { const res = findTreeObjById(item.children, targetType, target); if (res) return res; } } return null; // 无匹配}/** * 提取树形结构中所有type为true的节点(迭代版,避免栈溢出) * @param {Array} tree 树形结构数组 * @param {string} childrenKey 子节点字段名 * @param {string} key 判断键值 * @param flagValue 判断键值 * @returns {Array} 平级的符合条件的节点数组 */export function getNodesIterative(tree, childrenKey, key, flagValue) { const result = []; // 用栈存储待遍历的节点(初始为根节点数组) const stack = [...tree]; while (stack.length > 0) { const node = stack.pop(); // 栈顶取出节点(也可用shift()实现队列,效率略低) // 筛选type为true的节点 if (node[key] === flagValue) { result.push({ ...node }); } // 子节点入栈(继续遍历) if (Array.isArray(node[childrenKey])) { stack.push(...node[childrenKey]); } } return result;}export default { findTreeObjById, getNodesIterative}
这个功能在两个项目中都用到了,记录一下,顺便提醒自己:这段代码写了有两三年了,逻辑上没什么大问题,但代码结构上还有优化空间,后面记得重构一下。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8