写在前面#
对于经常需要开发企业管理后台的前端开发来说,必不可少的需要使用表格对于数据进行操作,在对于现有项目进行代码优化时,封装一些公共的Hooks.
本篇文章为useTableRowSelection.js
基于个人项目环境进行封装的Hooks,仅以本文介绍封装Hooks思想心得,故相关代码可能不适用他人
项目环境#
Vue3.x + Ant Design Vue3.x + Vite3.x
封装思考:为什么封装useTableRowSelection.js#
首先, 基于Hooks(useTableData.js
、useQueryParams.js
)的封装,作为管理后台表格常见操作的批量删除、批量编辑相关,封装useTableRowSelection.js
并使用,可以仅在页面 table 组件中绑定的 :row-selection="rowSelection"
进行处理即可,不用在每个单页面文件中写表格选择的重复代码。另外,我们可以在Ant Design Vue
或Ant Design
官方的组件文档中,看到Table组件的选择示例,在进行翻页后,先前选择的数据就被清除了。好吧,现在官方已经能够提供这个能力了,泪奔~
PS: 至少2022年8月份肯定没有这个能力,因为我的Hooks在那个时候封装的,只是最近稍微空闲些,才又开始写文章~
封装分解:表格多选#
由于封装本Hooks时,官方未提供对应跨页多选的能力,而现在官方已经提供了相关能力,故相关代码仅介绍封装思想。
多选时,将需要进行跨页选择的表格,对于已选择的数据进行缓存。checkBox需要考虑选择和全选(每个分页表头的全选)功能。
对于已选择的 selectKeys
、 selectItems
进行处理,并暴露出来,之后提供给需要表格选择的页面进行数据处理,包括但不限于请求业务接口需要进行的传参,或回显。
// 选择
onSelect: (record, selected) => {
if (selected) {
selectKeys.value.push(record[rowId]);
if ($cacheItem) selectItems.value = [...selectItems.value, ...[record]];
} else {
const index = selectKeys.value.findIndex(key => key === record[rowId]);
if (index >= 0) {
selectKeys.value.splice(index, 1);
if ($cacheItem) {
const $cacheSelectItems = selectItems.value;
$cacheSelectItems.splice(index, 1);
selectItems.value = [...$cacheSelectItems];
}
}
}
},
// 全选
onSelectAll: selected => {
if (selected) {
tableData.value.forEach(item => {
const index = selectKeys.value.findIndex(id => item[rowId] === id);
if (index < 0) {
selectKeys.value.push(item[rowId]);
if ($cacheItem) selectItems.value = [...selectItems.value, ...[item]];
}
});
} else {
tableData.value.forEach(item => {
const index = selectKeys.value.findIndex(key => key === item[rowId]);
if (index >= 0) {
selectKeys.value.splice(index, 1);
if ($cacheItem) {
const $cacheSelectItems = selectItems.value;
$cacheSelectItems.splice(index, 1);
selectItems.value = [...$cacheSelectItems];
}
}
});
}
},
封装思考:完整的表格选择Hooks#
一开始出于和useTableData
、useQueryParams
相关表格Hooks的使用目的,减少项目内重复代码的考虑。完整的useTableRowSelection
应该需要同时考虑表格内多选和单选。虽然多选能实现单选的功能但是页面内多选和单选的UI呈现方式还是有区别的~,因此Hooks接收表格源数据tableData
、筛选类型selectType
、唯一KeyrowId
以及是否开启缓存(即跨页筛选,毕竟不是所有的表格都需要跨页筛选)cacheItem = false
。
在这里,封装的Hooks,和现有官方提供的能力有一点点的不同,官方文档内表格的筛选是跨页选择的,考虑实际业务(当然仅个人公司涉及业务场景考虑,可能不是通用的),仅在需要进行选择人员参加某实际业务项目或调整相关可见范围等需求。
封装分解:暴露clearKeys、isEmptyKeys#
对外暴露 clearKeys 方法,用于清除表格筛选,对外暴露 isEmptyKeys 方法,用于判断表格的筛选是否为空
useTableRowSelection.js完整代码#
import { computed, ref, shallowRef, isRef } from 'vue';
import { ROW_SELECT_TYPE } from '@/enums';
export function useTableRowSelection(tableData, selectType, rowId, cacheItem = false) {
if (!isRef(tableData)) throw new Error('参数 tableData 必须为 Ref 类型');
const selectKeys = ref([]);
const selectItems = shallowRef([]);
const rowSelection = computed(() => {
const $selectType = isRef(selectType) ? selectType.value : selectType;
const $cacheItem = isRef(cacheItem) ? selectType.value : cacheItem;
if ($selectType === ROW_SELECT_TYPE.CHECKBOX) {
return {
onSelect: (record, selected) => {
if (selected) {
selectKeys.value.push(record[rowId]);
if ($cacheItem) selectItems.value = [...selectItems.value, ...[record]];
} else {
const index = selectKeys.value.findIndex(key => key === record[rowId]);
if (index >= 0) {
selectKeys.value.splice(index, 1);
if ($cacheItem) {
const $cacheSelectItems = selectItems.value;
$cacheSelectItems.splice(index, 1);
selectItems.value = [...$cacheSelectItems];
}
}
}
},
onSelectAll: selected => {
if (selected) {
tableData.value.forEach(item => {
const index = selectKeys.value.findIndex(id => item[rowId] === id);
if (index < 0) {
selectKeys.value.push(item[rowId]);
if ($cacheItem) selectItems.value = [...selectItems.value, ...[item]];
}
});
} else {
tableData.value.forEach(item => {
const index = selectKeys.value.findIndex(key => key === item[rowId]);
if (index >= 0) {
selectKeys.value.splice(index, 1);
if ($cacheItem) {
const $cacheSelectItems = selectItems.value;
$cacheSelectItems.splice(index, 1);
selectItems.value = [...$cacheSelectItems];
}
}
});
}
},
getCheckboxProps: record => {
return {
disabled: record.disabled,
};
},
selectedRowKeys: selectKeys.value,
type: ROW_SELECT_TYPE.CHECKBOX,
};
} else {
return {
onSelect: record => {
selectKeys.value = [record[rowId]];
if ($cacheItem) selectItems.value = [record];
},
selectedRowKeys: selectKeys.value,
getCheckboxProps: record => {
return {
disabled: record.disabled,
};
},
type: ROW_SELECT_TYPE.RADIO,
};
}
});
const clearKeys = () => {
selectKeys.value.length = 0;
selectItems.value = [];
};
const isEmptyKeys = () => {
return selectKeys.value.length === 0;
};
return {
selectItems,
selectKeys,
rowSelection,
clearKeys,
isEmptyKeys,
};
}
实际使用:参考示例#
- 页面组件
Page.vue
<a-table
size="small"
class="mt-2"
row-key="id"
:data-source="tableData"
:loading="loading"
:columns="columns"
:row-selection="rowSelection"
@change="onTableChange"
/>
- 具体使用
const { rowSelection, selectKeys, clearKeys, isEmptyKeys } = useTableRowSelection(
tableData,
ROW_SELECT_TYPE.CHECKBOX,
'id',
);
const confirmSelect = () => {
if (isEmptyKeys()) { // 确认按钮,业务场景,表格筛选项非空校验
proxy.$message.warning('请选择****');
return;
}
const selectItem = tableData.value.find(item => item.id === selectKeys.value[0]);
emits('on-select', {
id: selectItem.id,
});
closeModal();
};
const closeModal = () => {
clearKeys();
resetParams(); // 还记得这个吗~`useQueryParams.js`Hooks内提供的方法~
};