问题描述:
1、el-tree 勾选反显,原本整个父子节点都勾选状态,新增一个子节点,因为父节点是全选状态,所以新增的子节点也呈勾选状态,实际是未勾选状态。
2、用的是default-checked-keys 回显
数据更新后,必须在 $nextTick 中调用 setCheckedKeys,这样才能确保树组件正确回显勾选状态。
解决思路:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 回显不生效 | default-checked-keys 非响应式 | 使用 setCheckedKeys |
| 设置时机不对 | DOM 未渲染完成 | 包裹在 $nextTick 中 |
| 传了父节点 id | 父节点会强制全选,并级联勾选所有子节点 | 只传叶子节点 id 给 setCheckedKeys |
- 核心逻辑
- 先处理子节点,再根据子节点状态回推父节点
- 只传叶子节点 id 给 setCheckedKeys,父节点状态由组件自动计算
markTreeChecked返回的是树数据本身,不会影响setCheckedKeys的行为
这样即使后端返回的数据中新增了子节点,父节点也能正确呈现半选状态。
html
javascript
<el-tree
:data="list"
show-checkbox
node-key="id"
ref="tree"
default-expand-all
:props="{label: 'name',value: 'id'}"
></el-tree>
获取数据及回显调用(created 或 mounted)
javascript
// 获取树的数据
getMenuTree()
.then(res => {
this.list = res.data.data;
// 获取已勾选的数据id
return getEchoData();
})
.then(res => {
// 处理回显
let menuIds = [];
if (res.data.data.menuIds && res.data.data.menuIds.length > 0) {
menuIds = res.data.data.menuIds.split(',');
}
// 根据返回的 menuIds 处理树数据,标记每个节点的选中状态(用于界面展示)
this.markTreeChecked(this.list, menuIds);
this.$nextTick(() => {
if (this.$refs.tree) {
// 只传叶子节点 id,避免父节点强制全选
const leafIds = this.filterLeafIds(this.list);
this.$refs.tree.setCheckedKeys(leafIds);
}
});
});
methods
javascript
// 递归标记树节点的选中状态
markTreeChecked(treeData, checkedIds) {
treeData.forEach(node => {
if (node.children && node.children.length > 0) {
// 先处理子节点
const childCheckedCount = this.markTreeChecked(node.children, checkedIds);
// 根据子节点选中数量,计算当前父节点状态
const allChecked = childCheckedCount === node.children.length;
const noneChecked = childCheckedCount === 0;
node.checked = allChecked;
node.halfChecked = !allChecked && !noneChecked;
} else {
// 叶子节点:直接判断是否在选中列表中
node.checked = checkedIds.includes(node.id);
}
});
}
// 收集所有选中的叶子节点 id
filterLeafIds(treeData) {
const leafIds = [];
const traverse = (nodes) => {
nodes.forEach(node => {
if (node.children && node.children.length > 0) {
traverse(node.children);
} else if (node.checked) {
leafIds.push(node.id);
}
});
};
traverse(treeData);
return leafIds;
}