表格Table实现前端全选所有功能方案示例(包含非当前页)

 更新时间:2024年02月01日 09:10:15   作者:小皇帝James  
这篇文章主要为大家介绍了表格Table实现前端全选所有功能,包括非当前页的方案示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

最近两家公司都遇到了全选全页+批量操作的功能场景,即点击全选所有的时候需要勾选所有数据包括非当前页的。

方案

如果纯前端分页可以参考 antdv.table,一般主流的组件库都给封装好了,全选所有时设置pageSize为无穷大并调用列表接口得到全量数据赋值给selectedRowKeys即可。但是这套方案最大的问题在于点击全选所有时需要等待后端接口返回,这样的交互延迟是无法忍受的!且全选所有+批量操作两次请求的服务端资源浪费也是巨大的。

因此基于后端分页的前提,提出了另一套合理解决方案:

通过isAll判断是否为全选所有,如果是的话配合excludeIds、否则配合includeIds的值完成返显。最后业务中调用批量操作接口的时候还需要传筛选项。

实现

使用框架 vue3 + antdv

代码如下,框架为 vue3 + antdv

// CTable
<template>
  <a-table
    v-bind="$attrs"
    :columns="columns"
  >
    <template #headerCell="{ column }" v-if="!$slots.headerCell">
      <template v-if="column.dataIndex === '_checkbox_'">
        <CTableHeaderCheckbox
          ref="cTableHeaderCheckboxRef"
          :rowKey="rowKey"
          :dataSource="dataSource"
          v-model:isAll="isAll"
          v-model:includeIds="includeIds"
          v-model:excludeIds="excludeIds"
          :judgeToggleIsAll="judgeToggleIsAll"
        />
      </template>
    </template>
    <template #bodyCell="{ record, column }" v-if="!$slots.bodyCell">
      <template v-if="column.dataIndex === '_checkbox_'">
        <CTableBodyCheckbox
          :record="record"
          :rowKey="rowKey"
          :isAll="isAll"
          :includeIds="includeIds"
          :excludeIds="excludeIds"
          :judgeToggleIsAll="judgeToggleIsAll"
        />
      </template>
    </template>
    <template v-for="(_, name) in $slots" :key="name" #[name]="slotProps">
      <slot :name="name" v-bind="slotProps" v-if="name === 'headerCell' && slotProps.column.dataIndex === '_checkbox_'">
        <CTableHeaderCheckbox
          ref="cTableHeaderCheckboxRef"
          :rowKey="rowKey"
          :dataSource="dataSource"
          v-model:isAll="isAll"
          v-model:includeIds="includeIds"
          v-model:excludeIds="excludeIds"
          :judgeToggleIsAll="judgeToggleIsAll"
        />
      </slot>
      <slot :name="name" v-bind="slotProps" v-if="name === 'bodyCell' && slotProps.column.dataIndex === '_checkbox_'">
        <CTableBodyCheckbox
          :record="slotProps.record"
          :rowKey="rowKey"
          :isAll="isAll"
          :includeIds="includeIds"
          :excludeIds="excludeIds"
          :judgeToggleIsAll="judgeToggleIsAll"
        />
      </slot>
      <slot :name="name" v-bind="slotProps" v-else></slot>
    </template>
  </a-table>
</template>

<script lang="ts" setup>
import { Table, TableColumnProps } from 'ant-design-vue';
import CTableHeaderCheckbox from './CTableHeaderCheckbox.vue';
import CTableBodyCheckbox from './CTableBodyCheckbox.vue';

const props = withDefaults(
  defineProps<{
    columns: TableColumnProps[],
    allSelection?: {
      onCheckboxChange:(params) => void,
    } | null,
  }>(),
  {
    columns: () => [],
    allSelection: null,
  },
);

const $attrs = useAttrs() as any;
const $slots = useSlots();

const cTableHeaderCheckboxRef = ref();
const columns = computed(() => {
  if (props.allSelection) {
    return [
      {
        title: '多选',
        dataIndex: '_checkbox_',
        fixed: 'left',
        width: 48,
        customHeaderCell: () => ({ class: 'ant-table-checkbox-column' }),
      },
      ...props.columns,
    ];
  }
  return props.columns;
});
// 是否全选所有
const isAll = ref(false);
// 未全选所有时勾选数据
const includeIds = ref<string[]>([]);
// 全选所有时反选数据
const excludeIds = ref<string[]>([]);
const rowKey = computed(() => $attrs.rowKey || $attrs['row-key']);
const dataSource = computed(() => $attrs.dataSource || $attrs['data-source']);
// 表单数据可能存在disabled不可选择状态,此时需要后端返回enabledTotal帮助判断
const total = computed(() => $attrs.pagination?.enabledTotal || $attrs.pagination?.total || $attrs.enabledTotal || $attrs.total);
// 已勾选总数,帮助业务展示
const checkedTotal = computed(() => (isAll.value ? total.value - excludeIds.value.length : includeIds.value.length));
// 当选择数据发生改变时,需要判断是否切换全选状态
const judgeToggleIsAll = () => {
  if (isAll.value && excludeIds.value.length && excludeIds.value.length === total.value) {
    isAll.value = false;
    includeIds.value = [];
    excludeIds.value = [];
  }
  if (!isAll.value && includeIds.value.length && includeIds.value.length === total.value) {
    isAll.value = true;
    includeIds.value = [];
    excludeIds.value = [];
  }
};
// 当源数据发生改变时,手动重置选择框状态
const onResetCheckbox = () => {
  cTableHeaderCheckboxRef.value.handleMenu({ key: Table.SELECTION_NONE });
};

// 有任何选择变化时,同步回传给父组件
watch(
  [isAll, includeIds, excludeIds],
  () => {
    props.allSelection?.onCheckboxChange?.({
      isAll: isAll.value,
      includeIds: includeIds.value,
      excludeIds: excludeIds.value,
      checkedTotal: checkedTotal.value,
    });
  },
  { deep: true },
);

defineExpose({
  onResetCheckbox,
});
</script>

判断slots是否存在headerCell和bodyCell

vue 模版里需要额外判断slots是否存在headerCell和bodyCell,如果存在的话需要透传动态插槽,否则通过具名插槽传入。

// CTableHeaderCheckbox
<template>
  <a-checkbox
    :checked="isCurrentChecked"
    :indeterminate="isCurrentIndeterminate"
    :disabled="isCurrentDisabled"
    @change="onCheckboxChange"
  />
  <a-dropdown
    :disabled="isCurrentDisabled"
  >
    <CIcon
      class="ml-2 cursor-pointer"
      icon="triangle-down-o"
      :size="12"
      color="#C9CCD0"
    />
    <template #overlay>
      <a-menu @click="handleMenu">
        <a-menu-item :key="Table.SELECTION_ALL">全选所有</a-menu-item>
        <a-menu-item :key="Table.SELECTION_INVERT">反选当页</a-menu-item>
        <a-menu-item :key="Table.SELECTION_NONE">清空所有</a-menu-item>
      </a-menu>
    </template>
  </a-dropdown>
</template>

<script lang="ts" setup>
import { Table } from 'ant-design-vue';

const props = withDefaults(
  defineProps<{
    rowKey: string,
    dataSource: any[],
    isAll: boolean,
    includeIds: string[],
    excludeIds: string[],
    judgeToggleIsAll:() => void,
  }>(),
  {},
);
const emit = defineEmits(['update:isAll', 'update:includeIds', 'update:excludeIds']);

const isAll = computed({
  get: () => props.isAll,
  set: (val) => {
    emit('update:isAll', val);
  },
});
const includeIds = computed({
  get: () => props.includeIds,
  set: (val) => {
    emit('update:includeIds', val);
  },
});
const excludeIds = computed({
  get: () => props.excludeIds,
  set: (val) => {
    emit('update:excludeIds', val);
  },
});
const isCurrentChecked = computed(() => {
  const ids = props.dataSource?.map((item) => item[props.rowKey]);
  if (!ids.length) return false;
  return isAll.value ? !ids.some((id) => excludeIds.value.includes(id)) : ids.every((id) => includeIds.value.includes(id));
});
const isCurrentIndeterminate = computed(() => {
  const ids = props.dataSource?.map((item) => item[props.rowKey]);
  if (!ids.length) return false;
  if (isAll.value) {
    return !ids.every((id) => excludeIds.value.includes(id)) && !isCurrentChecked.value;
  } else {
    return ids.some((id) => includeIds.value.includes(id)) && !isCurrentChecked.value;
  }
});
const isCurrentDisabled = computed(() => !props.dataSource?.map((item) => item[props.rowKey]).length);
const handleMenu = ({ key }) => {
  const ids = props.dataSource?.map((item) => item[props.rowKey]);
  if (key === Table.SELECTION_INVERT) {
    // 数学意义的补集
    if (isAll.value) {
      excludeIds.value = [
        ...excludeIds.value.filter((id) => !ids.includes(id)),
        ...ids.filter((id) => !excludeIds.value.includes(id)),
      ];
    } else {
      includeIds.value = [
        ...includeIds.value.filter((id) => !ids.includes(id)),
        ...ids.filter((id) => !includeIds.value.includes(id)),
      ];
    }
    props.judgeToggleIsAll();
  } else {
    isAll.value = key === Table.SELECTION_ALL;
    includeIds.value = [];
    excludeIds.value = [];
  }
};
const onCheckboxChange = (e) => {
  const ids = props.dataSource?.map((item) => item[props.rowKey]);
  const { checked } = e.target;
  if (isAll.value) {
    excludeIds.value = checked ? excludeIds.value.filter((id) => !ids.includes(id)) : Array.from(new Set([...excludeIds.value, ...ids]));
  } else {
    includeIds.value = checked ? Array.from(new Set([...includeIds.value, ...ids])) : includeIds.value.filter((id) => !ids.includes(id));
  }
  props.judgeToggleIsAll();
};

defineExpose({
  handleMenu,
});
</script>

CTableBodyCheckbox 

// CTableBodyCheckbox
<template>
  <a-checkbox
    :checked="isAll ? !excludeIds.includes(record[rowKey]) : includeIds.includes(record[rowKey])"
    :disabled="record.disabled"
    @change="onCheckboxChange(record[rowKey])"
  />
</template>

<script lang="ts" setup>
const props = withDefaults(
  defineProps<{
    record: any,
    rowKey: string,
    isAll: boolean,
    includeIds: string[],
    excludeIds: string[],
    judgeToggleIsAll:() => void,
  }>(),
  {},
);

const onCheckboxChange = (keyId) => {
  const ids = props.isAll ? props.excludeIds : props.includeIds;
  const index = ids.indexOf(keyId);
  if (~index) {
    ids.splice(index, 1);
  } else {
    ids.push(keyId);
  }
  props.judgeToggleIsAll();
};
</script>

结论

如此一来,展示和交互逻辑就全部收拢在前端了,对于交互体验和服务端负载都是极大的改善。

以上就是表格Table实现前端全选所有功能方案示例的详细内容,更多关于Table表格全选功能的资料请关注脚本之家其它相关文章!

以上就是表格Table实现前端全选所有功能方案示例(包括非当前页)的详细内容,更多关于Table表格全选功能的资料请关注脚本之家其它相关文章!

相关文章

  • Vue3中正确使用ElementPlus的示例代码

    Vue3中正确使用ElementPlus的示例代码

    这篇文章主要介绍了Vue3中正确使用ElementPlus的示例代码,本文结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-01-01
  • 详解vue为什么要求组件模板只能有一个根元素

    详解vue为什么要求组件模板只能有一个根元素

    这篇文章主要介绍了vue为什么要求组件模板只能有一个根元素,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-07-07
  • vue.js实现格式化时间并每秒更新显示功能示例

    vue.js实现格式化时间并每秒更新显示功能示例

    这篇文章主要介绍了vue.js实现格式化时间并每秒更新显示功能,结合实例形式分析了vue.js时间格式化显示与基于定时器进行实时更新的相关操作技巧,需要的朋友可以参考下
    2018-07-07
  • 基于Vue实现树形穿梭框的示例代码

    基于Vue实现树形穿梭框的示例代码

    这篇文章主要为大家介绍了如何利用Vue实现一个树形穿梭框,elementUI和ant-d组件库的穿梭框组件效果都不是很好,所以本文将利用一个新的插件来实现,需要的可以参考一下
    2022-04-04
  • Vue3中使用Supabase Auth方法详解

    Vue3中使用Supabase Auth方法详解

    这篇文章主要为大家介绍了Vue3中使用Supabase Auth方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Vue + SpringBoot 实现文件的断点上传、秒传存储到Minio的操作方法

    Vue + SpringBoot 实现文件的断点上传、秒传存储到Minio的操作方法

    这篇文章主要介绍了Vue + SpringBoot 实现文件的断点上传、秒传存储到Minio的操作方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2024-06-06
  • Vue+tracking.js 实现前端人脸检测功能

    Vue+tracking.js 实现前端人脸检测功能

    这篇文章主要介绍了Vue+tracking.js 实现前端人脸检测功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04
  • 在 Vue 3 中设置 `@` 指向根目录的几种常见方法汇总

    在 Vue 3 中设置 `@` 指向根目录的几种常见方法汇总

    在 Vue 3 项目开发中,为了方便管理和引用文件路径,设置 @ 指向根目录是一项常见的需求,下面给大家分享在Vue3中设置 `@` 指向根目录的方法汇总,感兴趣的朋友一起看看吧
    2024-06-06
  • Vue.extend实现挂载到实例上的方法

    Vue.extend实现挂载到实例上的方法

    这篇文章主要介绍了Vue.extend实现挂载到实例上的方法,结合实例形式分析了Vue.extend实现挂载到实例上的具体操作步骤与相关实现技巧,需要的朋友可以参考下
    2019-05-05
  • 使用vue-cli webpack 快速搭建项目的代码

    使用vue-cli webpack 快速搭建项目的代码

    这篇文章主要介绍了vue-cli webpack 快速搭建项目的教程详解,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-11-11

最新评论