表格Table實(shí)現(xiàn)前端全選所有功能方案示例(包含非當(dāng)前頁(yè))
前言
最近兩家公司都遇到了全選全頁(yè)+批量操作的功能場(chǎng)景,即點(diǎn)擊全選所有的時(shí)候需要勾選所有數(shù)據(jù)包括非當(dāng)前頁(yè)的。

方案
如果純前端分頁(yè)可以參考 antdv.table,一般主流的組件庫(kù)都給封裝好了,全選所有時(shí)設(shè)置pageSize為無(wú)窮大并調(diào)用列表接口得到全量數(shù)據(jù)賦值給selectedRowKeys即可。但是這套方案最大的問(wèn)題在于點(diǎn)擊全選所有時(shí)需要等待后端接口返回,這樣的交互延遲是無(wú)法忍受的!且全選所有+批量操作兩次請(qǐng)求的服務(wù)端資源浪費(fèi)也是巨大的。
因此基于后端分頁(yè)的前提,提出了另一套合理解決方案:
通過(guò)isAll判斷是否為全選所有,如果是的話配合excludeIds、否則配合includeIds的值完成返顯。最后業(yè)務(wù)中調(diào)用批量操作接口的時(shí)候還需要傳篩選項(xiàng)。
實(shí)現(xiàn)
使用框架 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);
// 未全選所有時(shí)勾選數(shù)據(jù)
const includeIds = ref<string[]>([]);
// 全選所有時(shí)反選數(shù)據(jù)
const excludeIds = ref<string[]>([]);
const rowKey = computed(() => $attrs.rowKey || $attrs['row-key']);
const dataSource = computed(() => $attrs.dataSource || $attrs['data-source']);
// 表單數(shù)據(jù)可能存在disabled不可選擇狀態(tài),此時(shí)需要后端返回enabledTotal幫助判斷
const total = computed(() => $attrs.pagination?.enabledTotal || $attrs.pagination?.total || $attrs.enabledTotal || $attrs.total);
// 已勾選總數(shù),幫助業(yè)務(wù)展示
const checkedTotal = computed(() => (isAll.value ? total.value - excludeIds.value.length : includeIds.value.length));
// 當(dāng)選擇數(shù)據(jù)發(fā)生改變時(shí),需要判斷是否切換全選狀態(tài)
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 = [];
}
};
// 當(dāng)源數(shù)據(jù)發(fā)生改變時(shí),手動(dòng)重置選擇框狀態(tài)
const onResetCheckbox = () => {
cTableHeaderCheckboxRef.value.handleMenu({ key: Table.SELECTION_NONE });
};
// 有任何選擇變化時(shí),同步回傳給父組件
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,如果存在的話需要透?jìng)鲃?dòng)態(tài)插槽,否則通過(guò)具名插槽傳入。
// 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">反選當(dāng)頁(yè)</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) {
// 數(shù)學(xué)意義的補(bǔ)集
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>結(jié)論
如此一來(lái),展示和交互邏輯就全部收攏在前端了,對(duì)于交互體驗(yàn)和服務(wù)端負(fù)載都是極大的改善。
以上就是表格Table實(shí)現(xiàn)前端全選所有功能方案示例的詳細(xì)內(nèi)容,更多關(guān)于Table表格全選功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
以上就是表格Table實(shí)現(xiàn)前端全選所有功能方案示例(包括非當(dāng)前頁(yè))的詳細(xì)內(nèi)容,更多關(guān)于Table表格全選功能的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue.js實(shí)現(xiàn)格式化時(shí)間并每秒更新顯示功能示例
這篇文章主要介紹了vue.js實(shí)現(xiàn)格式化時(shí)間并每秒更新顯示功能,結(jié)合實(shí)例形式分析了vue.js時(shí)間格式化顯示與基于定時(shí)器進(jìn)行實(shí)時(shí)更新的相關(guān)操作技巧,需要的朋友可以參考下2018-07-07
Vue?+?SpringBoot?實(shí)現(xiàn)文件的斷點(diǎn)上傳、秒傳存儲(chǔ)到Minio的操作方法
這篇文章主要介紹了Vue?+?SpringBoot?實(shí)現(xiàn)文件的斷點(diǎn)上傳、秒傳存儲(chǔ)到Minio的操作方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-06-06
Vue+tracking.js 實(shí)現(xiàn)前端人臉檢測(cè)功能
這篇文章主要介紹了Vue+tracking.js 實(shí)現(xiàn)前端人臉檢測(cè)功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
在 Vue 3 中設(shè)置 `@` 指向根目錄的幾種常見方法匯總
在 Vue 3 項(xiàng)目開發(fā)中,為了方便管理和引用文件路徑,設(shè)置 @ 指向根目錄是一項(xiàng)常見的需求,下面給大家分享在Vue3中設(shè)置 `@` 指向根目錄的方法匯總,感興趣的朋友一起看看吧2024-06-06
Vue.extend實(shí)現(xiàn)掛載到實(shí)例上的方法
這篇文章主要介紹了Vue.extend實(shí)現(xiàn)掛載到實(shí)例上的方法,結(jié)合實(shí)例形式分析了Vue.extend實(shí)現(xiàn)掛載到實(shí)例上的具體操作步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-05-05
使用vue-cli webpack 快速搭建項(xiàng)目的代碼
這篇文章主要介紹了vue-cli webpack 快速搭建項(xiàng)目的教程詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11

