element-plus解决表格合并行选中问题

1,047 阅读1分钟

问题

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>

演示地址