前言
前两天写项目
的时候,要造一个树形
的表格,并且具有选中
功能的这么一个表格
,这对我这种具有十年代码经验的帅小伙(上班经验两年半)简直小菜一碟。于是我立马打开的element-plus官网,ctrl
+f
+table
,ctrl
+f
+树形
,找到了这个例子,并出色的完成了功能:
交完差,记下来又是快乐摸鱼时光。
但是没想到过了一天,组长
十分凝重的告诉我,“你这写的体验很差啊,连联动效果都没有”。我一脸蒙蔽,难道element-plus
的表格还有这个问题吗,于是打开自己写的例子测试
了一下:
纳尼!
,二级以下
,联动
就失去了效果。当然这并不能难到我,直接github
找issue
,寻求帮助,
好吧,有是有,但是并没什么用,看到官方还没修,也没大神留言解决方法
。但是看隔壁
的NaiveUI
是正常的:
干脆劝组长
换NaiveUI
吧
当然最后,我被组长说服了(骂死了),我觉得他说得对,这种方法怎么能得到提升呢,所以我觉得,改造这个组件
才是最好的方式。
开始改造
刚开始并没有思路,但是从NaiveUI
那边得到了一丢丢思路,虽然四舍五入
等于零,但是已经足够我完成这个功能
了
总的来说,肯定是要重写它的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;
};
}
});
然后,基本就是这样的效果:
可以看到除了全选
,其他效果基本是实现了,现在加一个全选
,这个还是比较好实现的,选中
的时候,把数据
全部set
到selectionRows
中就行了,取消全选
就是把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-column
的header
插槽,插入一个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-plus
的talbe
的树形bug
就修复了,然后就能继续摸鱼
。
附上在线运行地址
: