表格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為無窮大并調(diào)用列表接口得到全量數(shù)據(jù)賦值給selectedRowKeys即可。但是這套方案最大的問題在于點(diǎn)擊全選所有時(shí)需要等待后端接口返回,這樣的交互延遲是無法忍受的!且全選所有+批量操作兩次請(qǐng)求的服務(wù)端資源浪費(fèi)也是巨大的。
因此基于后端分頁(yè)的前提,提出了另一套合理解決方案:
通過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)插槽,否則通過具名插槽傳入。
// 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é)論
如此一來,展示和交互邏輯就全部收攏在前端了,對(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)文章
解決vue2.0動(dòng)態(tài)綁定圖片src屬性值初始化時(shí)報(bào)錯(cuò)的問題
下面小編就為大家分享一篇解決vue2.0動(dòng)態(tài)綁定圖片src屬性值初始化時(shí)報(bào)錯(cuò)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-03-03
前端+接口請(qǐng)求實(shí)現(xiàn)vue動(dòng)態(tài)路由
在Vue應(yīng)用中,結(jié)合前端和后端接口請(qǐng)求實(shí)現(xiàn)動(dòng)態(tài)路由,可根據(jù)用戶權(quán)限動(dòng)態(tài)生成路由,提高安全性與靈活性,本文就來介紹一下前端+接口請(qǐng)求實(shí)現(xiàn)vue動(dòng)態(tài)路由,感興趣的可以了解一下2024-09-09
vue3中實(shí)現(xiàn)異步組件的方法實(shí)例
前端開發(fā)經(jīng)常遇到異步的問題,請(qǐng)求函數(shù)、鏈接庫(kù)等,下面這篇文章主要給大家介紹了關(guān)于vue3中實(shí)現(xiàn)異步組件的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06
vant-ui組件調(diào)用Dialog彈窗異步關(guān)閉操作
這篇文章主要介紹了vant-ui組件調(diào)用Dialog彈窗異步關(guān)閉操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-11-11
解決vue cli4升級(jí)sass-loader(v8)后報(bào)錯(cuò)問題
這篇文章主要介紹了解決vue cli4升級(jí)sass-loader(v8)后報(bào)錯(cuò)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07
Vue實(shí)現(xiàn)懸浮框自由移動(dòng)+錄音功能的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Vue實(shí)現(xiàn)懸浮框自由移動(dòng)+錄音功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動(dòng)手嘗試一下2022-07-07
Vue中rem與postcss-pxtorem的應(yīng)用詳解
這篇文章主要介紹了Vue中rem與postcss-pxtorem的應(yīng)用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11

