实现对数据库表和表字段的勾选,数据库表包含了表字段,后端一次性返回所有表的数据。前端需要自行对表做翻页处理。当用户勾选完需要的表和字段后,再把勾选后的数据发送给后端。
需求拆分
- 前端翻页功能
- 跨页级联勾选功能
前端翻页功能
需要实现前端翻页的部分是数据库表,进入页面后,默认自动向后端获取一次 搜索后的表数据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); },总结
以上就实现了纯前端翻页+跨页级联勾选效果。纯前端翻页是把先把搜索数据保存在本地变量中,然后通过数组的切片功能,根据当前页数和大小进行切片,再把结果保存到当前页中。跨页级联选择是先把所有数据保存在本地变量中,再分析如何确定各个勾选框状态,最后通过一个保存了已勾选的表和字段状态的变量进行状态更新。这其中的重点便是理解如何构建和维护这样一个变量。
另外,纯前端翻页是目的是为了能够完成跨页级联勾选功能,因为只有后端返回了所有的数据给我们,我们才能计算出全部选择勾选框的状态。数据量过大的情况下,后端即使返回了所有数据给我们,浏览器也无法进行大量数据的保存和计算。这种情况下,需要采取另外的方案。