问题
el-table自带的选中列<el-table-column type="selection" width="55" />在无合并行时表现良好,但在合并行的表格,选中合并行只选中了合并行的第一条,因此会导致表头的checkbox和所有行的checkbox联动出现问题。
举个例子:
const tableData: User[] = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom1',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-08',
name: 'Tom2',
address: 'No. 189, Grove St, Los Angeles',
},
]
以上数据通过判断name渲染合并表格,则第一条和第二条会合并,表格会有三行(一个合并行,两个单独行)。当选中后两行的前提下再选中第一行,顶部checkbox状态应为全选但仍为半选中。
解决方案
期望当选中的是['Tom', 'Tom1', 'Tom2']时,顶部checkbox为选中状态,这里可以将表头的checkbox和所有行的checkbox联动以及展示逻辑抽离,首先将当前页的表格数据做一个去重处理,当去重后的表格数据长度等于选中的长度,则可以认为是全选, 而行选中的状态通过当前行是否包含在选中里来判断。如下:
/**
*
* @param itemKey 每行唯一标识符
* @returns 顶部checkbox选中状态-getState().checked 顶部checkbox半选中状态-getState().half
*/
export const useSel = (itemKey: string) => {
const selList = ref<(string | number)[]>([])
const onChange = (e, item: any) => {
if (e) {
selList.value = [...new Set([...selList.value, item[itemKey]])]
} else {
const index = selList.value.indexOf(item[itemKey])
selList.value.splice(index, 1)
}
}
const onTopChange = (e, list: any[]) => {
const ids = list.map((d) => d[itemKey])
if (e) {
selList.value = [...new Set([...selList.value, ...ids])]
} else {
selList.value = selList.value.filter((id) => !~ids.indexOf(id))
}
}
const getState = (list: any[]) => {
const ids = [...new Set(list.map((d) => d[itemKey]))]
const checkIds = selList.value.filter((d) => ~ids.indexOf(d))
return {
half: !!(checkIds.length && checkIds.length < ids.length),
checked: !!(checkIds.length && checkIds.length === ids.length)
}
}
return {
onTopChange, onChange, getState, selList
}
}
应用
<el-table :data="tableData" :span-method="objSpanMethod" border>
<el-table-column width="50" fixed>
<template #header>
<el-checkbox :indeterminate="getState(tableData).half" :model-value="getState(tableData).checked" @change="onTopChange($event, tableData)" />
</template>
<template v-slot="{ row }">
<el-checkbox @click.stop :model-value="!!~selList.indexOf(row.name)" @change="onChange($event, row)"></el-checkbox>
</template>
</el-table-column>
<el-table-column property="name" label="Name" width="120" />
<el-table-column property="address" label="Address" show-overflow-tooltip />
</el-table>
<script lang="ts" setup>
// 忽略其他非必要逻辑...
const tableData = Array.from({length: 4}, (_,i) => ({
date: '2016-05-03',
name: i > 1 ? 'Tom'+i : 'Tom',
address: 'No. 189, Grove St, Los Angeles',
}))
const { onTopChange, onChange, getState, selList } = useSel('name')
const { rowSpan, objSpanMethod } = useTableMergeSelf([0, 1])
rowSpan(tableData, 'name')
</script>