解决element-plus的树形表格选中的联动问题

1,915 阅读3分钟

前言

前两天写项目的时候,要造一个树形的表格,并且具有选中功能的这么一个表格,这对我这种具有十年代码经验的帅小伙(上班经验两年半)简直小菜一碟。于是我立马打开的element-plus官网,ctrl+f+table,ctrl+f+树形,找到了这个例子,并出色的完成了功能:

image.png

image.png

交完差,记下来又是快乐摸鱼时光。 但是没想到过了一天,组长十分凝重的告诉我,“你这写的体验很差啊,连联动效果都没有”。我一脸蒙蔽,难道element-plus的表格还有这个问题吗,于是打开自己写的例子测试了一下:

动画.gif

纳尼!二级以下联动就失去了效果。当然这并不能难到我,直接githubissue,寻求帮助,

image.png

好吧,有是有,但是并没什么用,看到官方还没修,也没大神留言解决方法。但是看隔壁NaiveUI是正常的:

动画.gif

干脆劝组长NaiveUI

image.png

当然最后,我被组长说服了(骂死了),我觉得他说得对,这种方法怎么能得到提升呢,所以我觉得,改造这个组件才是最好的方式。

开始改造

刚开始并没有思路,但是从NaiveUI那边得到了一丢丢思路,虽然四舍五入等于零,但是已经足够我完成这个功能

image.png

总的来说,肯定是要重写它的checkbox这个功能的,所以,先拿到插槽对象slorts,然后,手动插入一个el-table-column,并且在它的default插槽中放入一个ElCheckBox

const tableSlots = reactive({ default: slots.default });

onMounted(() => {
    tableSlots.default = () => {
      const columnsVNodes = slots.default();
      columnsVNodes.unshift(
        <ElTableColumn
        >
          {{
              return (
                <ElCheckbox/>
              );
            },
          }}
        </ElTableColumn>,
      );
      return columnsVNodes;
    };
});

这样就自己伪造了一个checkbox,然后我们开始加联动的效果,其实这个效果也比较容易思考,中间状态就是判断选中的数组长度小于这个节点的children长度,当等于的时候,就是选中状态,所以添加两个变量用来存储选中中间态的对象。

const selectionRows = ref<Map<string, any>>(new Map());
const indeterminateRows = ref<Map<string, any>>(new Map());

当点击列表中checkbox时,判断是否有下一级,如果有,就把下一级全部选中,如果selectionRows长度等于下一级长度的话,就是选中状态

// tree-table.vue
const setDeepCheck = (row, checked) => {
  const rowKey = row[props.rowKey];
  if (checked) {
    selectionRows.value.set(rowKey, row);
  } else {
    selectionRows.value.delete(rowKey);
  }
  row[treeChildrenProp.value]?.forEach(child => {
    const rowKey = child[props.rowKey];
    const children = child[treeChildrenProp.value];
    if (checked) {
      selectionRows.value.set(rowKey, child);
    } else {
      selectionRows.value.delete(rowKey);
    }
    if (children) {
      children.forEach(child => {
        setDeepCheck(child, checked);
      });
    }
  });
};


onMounted(() => {
  if (props.selection) {
    if (!props.rowKey) {
      console.warn("please set the rowKey");
      return;
    }
    tableSlots.default = () => {
      const columnsVNodes = slots.default();
      columnsVNodes.unshift(
        <ElTableColumn
          className="checkbox-column"
          align="center"
          width={props.checkboxConfig.width || defaultCheckboxWidth}
        >
          {{
            default: ({ row }) => {
              const rowKey = row[props.rowKey];
              const isChecked = selectionRows.value.has(rowKey);

              return (
                <ElCheckbox
                  modelValue={isChecked}
                  onChange={(value) => {
                    setDeepCheck(row, value);
                  }}
                />
              );
            },
          }}
        </ElTableColumn>
      );
      return columnsVNodes;
    };
  }
});

选中的下一级的长度小于小于下一级长度时,上级就为中间态

onMounted(() => {
  if (props.selection) {
    if (!props.rowKey) {
      console.warn("please set the rowKey");
      return;
    }
    tableSlots.default = () => {
      const columnsVNodes = slots.default();
      columnsVNodes.unshift(
        <ElTableColumn
          className="checkbox-column"
          align="center"
          width={props.checkboxConfig.width || defaultCheckboxWidth}
        >
          {{
            default: ({ row }) => {
              const rowKey = row[props.rowKey];
              const isChecked = selectionRows.value.has(rowKey);
              const children = row[treeChildrenProp.value] as any[];
              const checkRows = children?.filter((child) =>
                selectionRows.value.has(child[props.rowKey])
              );
              if (
                checkRows &&
                selectionRows.value.size > 0 &&
                checkRows.length > 0 &&
                checkRows.length < children.length
              ) {
                indeterminateRows.value.set(rowKey, row);
              } else {
                if (
                  checkRows?.length > 0 &&
                  checkRows?.length === children?.length
                ) {
                  selectionRows.value.set(rowKey, row);
                } else if (checkRows?.length === 0) {
                  selectionRows.value.delete(rowKey);
                }
                indeterminateRows.value.delete(rowKey);
              }

              return (
                <ElCheckbox
                  modelValue={isChecked}
                  indeterminate={indeterminateRows.value.has(
                    row[props.rowKey]
                  )}
                  onChange={(value) => {
                    setDeepCheck(row, value);
                  }}
                />
              );
            },
            header: () => {
              const isIndeterminateRows =
                selectionRows.value.size > 0 &&
                selectionRows.value.size < dataCount.value;

              const isChecked =
                selectionRows.value.size > 0 &&
                selectionRows.value.size === dataCount.value;

              return (
                <ElCheckbox
                  modelValue={isChecked}
                  indeterminate={isIndeterminateRows}
                  onChange={(value) => {
                    tableData.value.forEach((row) => {
                      setDeepCheck(row, value);
                    });
                  }}
                />
              );
            },
          }}
        </ElTableColumn>
      );
      return columnsVNodes;
    };
  }
});

然后,基本就是这样的效果:

动画.gif

可以看到除了全选,其他效果基本是实现了,现在加一个全选,这个还是比较好实现的,选中的时候,把数据全部setselectionRows中就行了,取消全选就是把selectionRows清空,对于中间态,拿总数indeterminateRows的长度进行比较就行了,所以首先要拿到数据总数

// 拿到总数的函数
const getDataCount = async (data: any[], childProps: string) => {
  let dataCount = 0;
  const calculation = async data => {
    await Promise.resolve().then(async () => {
      for (const child of data) {
        const children = child[childProps];
        dataCount++;
        if (children && children.length > 0) {
          await calculation(children);
        }
      }
    });
  };

然后用el-table-columnheader插槽,插入一个checkbox

<ElTableColumn
...
>
{{
...
  header: () => {
   
    return (
      <ElCheckbox/>
    );
  },
}}
</ElTableColumn>

再加上判断checkbox状态的代码

<ElTableColumn
...
>
{{
...
  header: () => {
   const isIndeterminateRows =
   selectionRows.value.size > 0 &&
   selectionRows.value.size < dataCount.value;

   const isChecked =
   selectionRows.value.size > 0 &&
   selectionRows.value.size === dataCount.value;

    return (
        <ElCheckbox
          modelValue={isChecked}
          indeterminate={isIndeterminateRows}
          onChange={(value) => {
            tableData.value.forEach((row) => {
              setDeepCheck(row, value);
            });
          }}
        />)
   
  },
}}
</ElTableColumn>

这样element-plustalbe树形bug就修复了,然后就能继续摸鱼

image.png

附上在线运行地址:

tree-table