需求描述
实现对数据库表和表字段的勾选,数据库表包含了表字段,后端一次性返回所有表的数据。前端需要自行对表做翻页处理。当用户勾选完需要的表和字段后,再把勾选后的数据发送给后端。
需求拆分
- 前端翻页功能
- 跨页级联勾选功能
实现效果
具体实现
前端翻页功能
需要实现前端翻页的部分是数据库表,进入页面后,默认自动向后端获取一次 搜索后的表数据searchTableList
,获取到之后,计算当前页显示的表数据,计算公式为 curTableList = searchTableList.slice((page - 1) * size, page * size)
,page
,size
保存在 url
参数中。如果用户点击翻页,则重新计算当前页显示的表数据。如果用户搜索表,则重置页数为 1
后,获取 searchTableList
,并重新计算当前页显示的表数据。
流程图
关键代码
// 获取当前页表数据
getCurTableList() {
// 不发请求,根据 searchTableList 获取数据。模拟翻页
let page = Number(this.$route.query.page || 1);
let size = Number(this.$route.query.size || 10);
let total = this.searchTableList.length;
let maxPage = Math.ceil(total / size);
page = page > maxPage ? maxPage : page;
this.tablePageData = { page, size, total };
this.curTableList = this.searchTableList.slice((page - 1) * size, page * size);
},
跨页级联勾选功能
分析用户可进行的勾选操作的勾选框:
- 全部表勾选框,共有未选、部分先和全选有三种状态,未选和部分选状态点击可勾选全部表,全选状态点击可取消勾选全部表;
- 当前页表勾选框,共有未选、部分选和全选有三种状态,未选和部分选状态点击可勾选当前页全部表,全选状态点击可取消勾选当前页全部表;
- 单个表勾选框,共有未选、部分选和全选有三种状态,未选和部分选状态点击可勾选表中的全部字段,全选状态点击可取消勾选表中的全部字段;
- 全部字段勾选框,共有未选、部分选和全选有三种状态,未选和部分选状态点击可勾选表中的全部字段,全选状态点击可取消勾选表中的全部字段;
- 单个字段勾选框,共有未选和已选有两种状态,未选状态点击可勾选表中的该字段,已选状态点击可取消勾选表中的该字段;
全部表数据 allTableList,该数据结构如下:
// 全部表
allTableList: [
// 每个表
{
"tableName": "table-name", // 表名
// 表中的全部字段
"fieldList": [
// 每个字段
{
"fieldName": "field-name", // 字段名
},
],
},
]
根据上面的信息分析,为了实现勾选状态的联动,最好的办法是把所有的已勾选项放在一个变量中保存,该变量保存了选中的表和字段,然后通过计算方法确定每个勾选框的状态。该勾选状态变量的结构如下:
// 选中的表和字段
checkedTableAndField: {
// 选中的表名
"table-name": [
"field-name", // 表中选中的字段
],
}
有了 checkedTableAndField
变量,所有的勾选框状态就都可以通过它来计算得到了。下面列出所有勾选框状态的计算方法:
- 全部表勾选框,全选状态=全部表状态都全选,部分选状态=本身非全选状态且有任一表是部分选或全选状态,未选状态=本身非全选且本身非部分选。转换为代码如下:
// 全部表是否全选
isTableAllChecked() {
return this.allTableList.length && this.allTableList.every(tableObj => this.isPerTableAllChecked(tableObj));
},
// 全部表是否部分选
isTablePartChecked() {
return !this.isTableAllChecked && this.allTableList.some(tableObj => this.isPerTablePartChecked(tableObj) || this.isPerTableAllChecked(tableObj));
},
- 当前页表勾选框,全选状态=当前页表状态都全选,部分选状态=本身非全选状态且有任一当前页表是部分选或全选状态,未选状态=本身非全选且本身非部分选。转换为代码如下:
// 当前页的表是否全选
isCurTableAllChecked() {
return this.curTableList.length && this.curTableList.every(tableObj => this.isPerTableAllChecked(tableObj));
},
// 当前页的表是否部分选
isCurTablePartChecked() {
return !this.isCurTableAllChecked && this.curTableList.some(tableObj => this.isPerTablePartChecked(tableObj) || this.isPerTableAllChecked(tableObj));
},
- 单个表勾选框,全选状态=表中的全部字段状态都勾选,部分选状态=本身非全选状态且表中有任一字段勾选,未选状态=本身非全选且本身非部分选。转换为代码如下:
// 单个表是否全选
isPerTableAllChecked(tableObj) {
let checkedFieldArr = vm.checkedTableAndField[tableObj.tableName];
return checkedFieldArr && checkedFieldArr.length === tableObj.fieldList.length;
},
// 单个表是否部分选
isPerTablePartChecked(tableObj) {
let checkedFieldArr = vm.checkedTableAndField[tableObj.tableName];
return !vm.isPerTableAllChecked(tableObj) && checkedFieldArr && checkedFieldArr.length;
},
- 全部字段勾选框,全选状态=全部字段状态都勾选,部分选状态=本身非全选状态且有任一字段勾选,未选状态=本身非全选且本身非部分选。转换为代码如下:
// this.allFieldList 是当前选中的表的所有字段,这里省略该变量部分代码
// 当前已选中的字段的标识集合
curCheckedFieldArr() {
return this.checkedTableAndField[this.curTableName] || [];
},
// 全部字段是否全选
isFieldAllChecked() {
return this.allFieldList.length && this.allFieldList.length === this.curCheckedFieldArr.length;
},
// 全部字段是否部分选
isFieldPartChecked() {
return !this.isFieldAllChecked && this.curCheckedFieldArr.length;
}
- 单个字段勾选框,勾选状态=本身是勾选,未选状态=本身非勾选。转换为代码如下:
// 单个字段是否勾选
isFieldChecked(fieldName) {
return curCheckedFieldArr.includes(fieldName)
}
以上逐步分析了各个勾选框的状态如何确定,基本围绕着 checkedTableAndField
变量进行,那么当我们更新 checkedTableAndField
的数据,所有的勾选框状态都会随着它更新了。这里贴出来更新 checkedTableAndField
数据的部分代码(详情参考具体项目实现):
// 点击 选择全部表
checkAllTable() {
let vm = this;
let checkedTableAndField = {};
if (!vm.isTableAllChecked) {
vm.allTableList.forEach(tableObj => {
checkedTableAndField[tableObj.tableName] = tableObj.fieldList.map(item => item.sensTypeMain.fieldName);
});
} else {
vm.allTableList.forEach(tableObj => {
checkedTableAndField[tableObj.tableName] = [];
});
}
vm.checkedTableAndField = checkedTableAndField;
},
// 点击 选择当前页的表
checkCurTable() {
let vm = this;
let checkedTableAndField = {};
if (!vm.isCurTableAllChecked) {
vm.curTableList.forEach(tableObj => {
checkedTableAndField[tableObj.tableName] = tableObj.fieldList.map(item => item.sensTypeMain.fieldName);
});
} else {
vm.curTableList.forEach(tableObj => {
checkedTableAndField[tableObj.tableName] = [];
});
}
vm.checkedTableAndField = checkedTableAndField;
},
// 点击 选择单个表
checkPerTable(tableObj) {
let vm = this;
let checkedFieldArr = !vm.isPerTableAllChecked(tableObj) ? tableObj.fieldList.map(item => item.sensTypeMain.fieldName) : [];
this.$set(this.checkedTableAndField, tableObj.tableName, checkedFieldArr);
},
// 点击 选择当前显示的全部字段
checkCurAllField() {
let vm = this;
let checkedFieldArr = !vm.isFieldAllChecked ? vm.allFieldList.map(item => item.sensTypeMain.fieldName) : [];
this.$set(this.checkedTableAndField, this.curTableName, checkedFieldArr);
},
// 点击 选择字段
checkField(fieldName) {
let checkedFieldArr = this.checkedTableAndField[this.curTableName];
if (checkedFieldArr) {
let position = checkedFieldArr.indexOf(fieldName);
position > -1 ? checkedFieldArr.splice(position, 1) : checkedFieldArr.push(fieldName);
} else {
checkedFieldArr = [fieldName];
}
this.$set(this.checkedTableAndField, this.curTableName, checkedFieldArr);
},
总结
以上就实现了纯前端翻页+跨页级联勾选效果。纯前端翻页是把先把搜索数据保存在本地变量中,然后通过数组的切片功能,根据当前页数和大小进行切片,再把结果保存到当前页中。跨页级联选择是先把所有数据保存在本地变量中,再分析如何确定各个勾选框状态,最后通过一个保存了已勾选的表和字段状态的变量进行状态更新。这其中的重点便是理解如何构建和维护这样一个变量。
另外,纯前端翻页是目的是为了能够完成跨页级联勾选功能,因为只有后端返回了所有的数据给我们,我们才能计算出全部选择勾选框的状态。数据量过大的情况下,后端即使返回了所有数据给我们,浏览器也无法进行大量数据的保存和计算。这种情况下,需要采取另外的方案。