跳到主要内容

2024-12-22 小汇总

· 阅读需 3 分钟

级联选择

复选框树形选择器比较常见,比如antd里的TreeSelectCascader组件。当选择某一项时,有以下几种可能:

  1. 如果是选中,则它的下级全都会被选中,并且还需要判断他有没有父级,如果有父级,还需要判断父级是否也是全选中或者半选中状态;
  2. 如果是取消选中,它的下级也会全部被取消选中,并且也要判断父级(或者说是上级)的选中状态。

要实现一个updateSelectedStatus函数,它的前面如下:

enum SelectedStatus {
/** 未选中 */
Unselected,
/** 选中 */
Selected,
/** 半选中 */
Indeterminate,
}

interface Cascade {
id: string;
selectedStatus: SelectedStatus;
children?: Cascade[];
}

function updateSelectedStatus(
checked: boolean,
current: Cascade,
tree: Cascade[]
): Cascade[];

updateSelectedStatus的参数如下:

  • checked 当前项是不是选中状态;
  • current 当前项;
  • tree 整颗级联树;

返回值是路径数组,比如一个属性结构是:

1
|----1_1
|--------1_1_1
|--------1_1_2
|----1_2
|--------1_2_1
|--------1_2_2
2
|----2_1
|--------2_1_1
|--------2_1_2
|----2_2

如果一开始都是未选中状态,这时候我去选了1_2这一项。则:1_2和他的下级1_2_11_2_2都会被选中,并且1这一项将变成半选状态。返回值将会是:

[
{
id: '1_2',
selectedStatus: SelectedStatus.Selected,
children: [...]
},
{
id: '1',
selectedStatus: SelectedStatus.Indeterminate,
children: [...]
},
]

实现:

/** 更新父选择状态 */
function updateParentSelectedType (parent: Cascade) {
const children = parent.children;
/* 是不是半选 */
let hasIndeterminate = false;

if (children?.length) {
const checkedList = children.filter((item) => {
if (item.selectedType === SelectedType.Indeterminate) {
hasIndeterminate = true;
return false;
}
return item.selectedType === SelectedType.Selected;
}
);

if (hasIndeterminate) {
parent.selectedType = SelectedType.Indeterminate;
} else if (checkedList.length === children.length) {
parent.selectedType = SelectedType.Selected;
} else if (!checkedList.length) {
parent.selectedType = SelectedType.Unselected;
} else {
parent.selectedType = SelectedType.Indeterminate;
}
}
return parent;
};

function updateSelectedStatus(
checked: boolean,
current: Cascade,
tree: Cascade[],
) {
const _f = (
checked: boolean,
current: Cascade,
tree: Cascade[],
parent?: Cascade
): Cascade[] => {
// 存放更新的路径
const records: Cascade[] = [];

for (const child of tree) {
if (child.id === current.id) {
child.selectedType = checked
? SelectedType.Selected
: SelectedType.Unselected;

records.push(child);

/** 更新子项 */
if (child.children?.length) {
child.children.forEach((item) => {
_f(checked, item, child.children, child);
});
}

/** 更新父项 */
if (parent) {
records.push(updateParentSelectedType(parent));
}

// 找到后就可以返回了,不用再循环去找了
return records;
} else {
if (child.children?.length) {
const list = _f(checked, current, child.children, child);

// list 中有值说明在下级找到了 current,否则就是没找到
if (parent && list.length) {
return [...list, updateParentSelectedType(parent)];
}
return list;
}
}
}

return records;
};

return _f(checked, current, tree);
}

最大高度与纵向滚动