引言
在前文中,我们曾深入探讨了在修改数据后跨页时提醒用户可能丢失数据的问题。虽然这种方式对于一些场景是足够的,但当涉及选择框时,我们需要更为智能和高效的解决方案。在本文中,我们将分享一种基于 Element UI 的实际案例,旨在实现跨页保存选中项与禁选特定项的需求。通过以下详细讨论,你将了解到这一方案的实现原理及其用户体验效果。
问题背景
在许多 Web 应用中,数据分页是常见的操作方式。当用户在一个页面中选择了一些数据项,然后切换到另一页时,保持之前选中的项通常是用户友好的体验。同时,可能存在一些需要禁选的执行项,例如在某些状态下,用户不应该选择或执行某些操作:如当数据可以进行执行相关操作,并且需要消耗一定时间时,我们为了任务的完整执行,会在任务执行期间,禁止选中该任务的选择框(可以预防用户进行删除等操作),后续执行完毕可以恢复初始选中状态,也可以放弃选中状态。
方案设计与实现
在 Element UI 中,表格(Table)组件提供了丰富的特性和事件。可以利用这些特性和事件来实现跨页保存选中项和禁选执行项的需求。
实现
1.跨页保存
在许多 Web 应用中,数据分页是常见的操作方式。当用户在一个页面中选择了一些数据项,然后切换到另一页时,保持之前选中的项通常是用户友好的体验。
模板
<el-table
ref="multipleTable"
:data="tableData"
@select="handleSelectionChange"
@select-all="handleSelectionAll"
>
<el-table-column type="selection" align="center"></el-table-column>
<el-table-column prop="name" label="name" align="center"></el-table-column>
<el-table-column prop="age" label="age" align="center"></el-table-column>
</el-table>
<el-pagination
:background="true"
:current-page.sync="queryParams.page"
:page-size.sync="queryParams.pageSize"
layout="total,prev,pager,next,sizes"
:total="total"
:page-sizes="[5, 10, 20, 40]"
@size-change="getList"
@current-change="getList" />
脚本
data() {
return {
total: void 0,
queryParams: {
page: 1,
pageSize: 10,
},
allData: [],
tableData: [],
multipleSelection: [],
listId: [],
};
},
mounted() {
this.getList();
},
methods: {
getList() {
this.allData = [
{ name: 'A', age: 1 },
{ name: 'B', age: 2 },
{ name: 'C', age: 3 },
{ name: 'D', age: 4 },
{ name: 'E', age: 5 },
{ name: 'F', age: 6 },
{ name: 'G', age: 7 },
{ name: 'H', age: 8 },
{ name: 'I', age: 9 },
{ name: 'J', age: 10 },
{ name: 'K', age: 11 },
{ name: 'L', age: 12 },
{ name: 'M', age: 13 },
{ name: 'N', age: 14 },
{ name: 'O', age: 15 },
{ name: 'P', age: 16 },
{ name: 'Q', age: 17 },
{ name: 'R', age: 18 },
{ name: 'S', age: 19 },
{ name: 'T', age: 20 },
{ name: 'U', age: 21 },
{ name: 'V', age: 22 },
{ name: 'W', age: 23 },
{ name: 'X', age: 24 },
{ name: 'Y', age: 25 },
{ name: 'Z', age: 26 },
];
this.total = this.allData.length;
let currentPageIndex = (this.queryParams.page - 1) * this.queryParams.pageSize;
let currentPageSize = this.queryParams.pageSize - 1 || 1;
this.tableData = this.allData.slice(currentPageIndex, currentPageIndex + currentPageSize + 1);
this.listId = [];
this.tableData.forEach(item => this.listId.push(item.name));
this.$nextTick(() => {
this.tableData.forEach((item, index) => {
if (this.multipleSelection.findIndex(v => v == item.name) >= 0) {
this.$refs.multipleTable.toggleRowSelection(this.$refs.multipleTable.data[index], true);
}
});
});
},
// 全选
handleSelectionAll(val) {
if (val.length) {
const result = [];
this.listId.forEach(id => {
if (this.multipleSelection.every(item => item !== id)) result.push(id);
});
this.multipleSelection.push(...result);
} else {
this.listId.forEach(id => {
this.multipleSelection = this.multipleSelection.filter(item => item !== id);
});
}
},
// 单选
handleSelectionChange(rows, row) {
if (this.multipleSelection.find(item => item === row.name)) this.multipleSelection = this.multipleSelection.filter(item => item != row.name); // 过滤(删除)
else this.multipleSelection.push(row.name);
},
}
解析
getList
方法:
- 发送 HTTP GET 请求,获取数据(
/.../
是请求的地址,queryParams
是请求参数)。- 在请求成功的回调中,判断返回数据的状态是否为200,如果是,将返回的数据赋值给
tableData
。- 构建
listId
数组,存储tableData
中每一项的id
。- 利用
$nextTick
,确保在 Vue 更新 DOM 后执行,遍历tableData
,对于已经在multipleSelection
中的项,在表格中选中对应的行。
handleSelectionAll
方法:
- 接受一个参数
val
,即当前页选中的所有行数据。- 如果
val.length
大于 0,表示当前页有选中的行,遍历listId
,将不在multipleSelection
中的项添加到multipleSelection
中。- 如果
val.length
为 0,表示当前页没有选中的行,遍历listId
,将在multipleSelection
中的项从中移除。
handleSelectionChange
方法:
- 接受两个参数,
rows
是当前页选中的所有行数据,row
是当前操作的行数据。- 如果
multipleSelection
中已经存在row.id
,则将其从multipleSelection
中移除,否则将其添加到multipleSelection
中。
这些方法共同实现了跨页保存选中状态的功能,通过维护 multipleSelection
数组来保存用户选择的行的 id
,从而在表格分页切换时保持选中状态。
2.禁选执行项
数据拥有执行状态之类的字段,并且需要消耗一定时间时,我们为了任务的完整执行,会在任务执行期间,禁止选中该任务的选择框(以避免删除等操作)。执行完毕后可以恢复初始选中状态,也可以放弃选中状态。
以下代码选择的是后者,即在重新开始运行时,禁用该项选择框并且执行完毕后不重新选中。如果希望保留,在对应执行的函数(下述为 reloadTask
)中不删除 multipleSelection
中对应的id即可。
模板
<el-table
ref="multipleTable"
:data="tableData"
@select="handleSelectionChange"
@select-all="handleSelectionAll"
>
<el-table-column type="selection" align="center" :selectable="selectable"></el-table-column>
<el-table-column prop="name" label="name" align="center"></el-table-column>
<el-table-column prop="age" label="age" align="center"></el-table-column>
<el-table-column label="status">
<template slot-scope="scope">
<span
style="margin-right:10px; cursor: pointer;"
:class="scope.row.status !== '执行中' ? 'el-icon-caret-right' : 'el-icon-loading'"
size="small"
:disabled="scope.row.status === '执行中'"
@click="reloadTask(scope.row)"></span>
<el-tag :type="scope.row.status !== '执行中' ? '' : 'info'">{{scope.row.status}}</el-tag>
</template>
</el-table-column>
</el-table>
<el-pagination
:background="true"
:current-page.sync="queryParams.page"
:page-size.sync="queryParams.pageSize"
layout="total,prev,pager,next,sizes"
:total="total"
:page-sizes="[5, 10, 20, 40]"
@size-change="getList"
@current-change="getList" />
脚本
data() {
return {
total: void 0,
queryParams: {
page: 1,
pageSize: 10,
},
allData: [],
tableData: [],
multipleSelection: [],
listId: [],
loadingSelection: new Set(),
};
},
mounted() {
this.getList();
},
methods: {
getList(flag = false) {
if (!flag) {
this.allData = [
{ name: 'A', age: 1, status: '执行完毕' },
{ name: 'B', age: 2, status: '执行中' },
{ name: 'C', age: 3, status: '执行完毕' },
{ name: 'D', age: 4, status: '未执行' },
{ name: 'E', age: 5, status: '未执行' },
{ name: 'F', age: 6, status: '执行中' },
{ name: 'G', age: 7, status: '执行中' },
{ name: 'H', age: 8, status: '执行中' },
{ name: 'I', age: 9, status: '执行中' },
{ name: 'J', age: 10, status: '未执行' },
{ name: 'K', age: 11, status: '未执行' },
{ name: 'L', age: 12, status: '未执行' },
{ name: 'M', age: 13, status: '未执行' },
{ name: 'N', age: 14, status: '未执行' },
{ name: 'O', age: 15, status: '未执行' },
{ name: 'P', age: 16, status: '未执行' },
{ name: 'Q', age: 17, status: '未执行' },
{ name: 'R', age: 18, status: '未执行' },
{ name: 'S', age: 19, status: '未执行' },
{ name: 'T', age: 20, status: '未执行' },
{ name: 'U', age: 21, status: '未执行' },
{ name: 'V', age: 22, status: '未执行' },
{ name: 'W', age: 23, status: '未执行' },
{ name: 'X', age: 24, status: '未执行' },
{ name: 'Y', age: 25, status: '未执行' },
{ name: 'Z', age: 26, status: '未执行' },
];
}
this.total = this.allData.length;
let currentPageIndex = (this.queryParams.page - 1) * this.queryParams.pageSize;
let currentPageSize = this.queryParams.pageSize - 1 || 1;
this.tableData = this.allData.slice(currentPageIndex, currentPageIndex + currentPageSize + 1);
this.listId = [];
this.tableData.forEach(item => this.listId.push(item.name));
this.$nextTick(() => {
this.tableData.forEach((item, index) => {
if (this.multipleSelection.findIndex(v => v == item.name) >= 0) {
this.$refs.multipleTable.toggleRowSelection(this.$refs.multipleTable.data[index], true);
}
});
});
},
// 执行任务
reloadTask(row) {
this.multipleSelection = this.multipleSelection.filter(item => item !== row.name);
row.status = '执行中';
this.getList(true);
},
// 判断可选性
selectable(row) {
if (row.status !== '执行中') {
if (this.loadingSelection.has(row.name)) this.loadingSelection.delete(row.name);
return true;
} else {
this.loadingSelection.add(row.name);
return false;
}
},
// 全选
handleSelectionAll(val) {
if (val.length) {
const result = [];
this.listId.forEach(id => {
if (this.multipleSelection.every(item => item !== id) && !this.loadingSelection.has(id)) result.push(id);
});
this.multipleSelection.push(...result);
} else {
this.listId.forEach(id => {
this.multipleSelection = this.multipleSelection.filter(item => item !== id);
});
}
},
// 单选
handleSelectionChange(rows, row) {
if (this.multipleSelection.find(item => item === row.name)) this.multipleSelection = this.multipleSelection.filter(item => item != row.name); // 过滤(删除)
else this.multipleSelection.push(row.name);
},
},
解析
这部分代码经过修改后主要涉及到对行的可选性(selectable
方法)以及全选处理(handleSelectionAll
方法)。下面是对修改部分代码的详细解释:
selectable
方法:
selectable
方法用于确定给定行row
是否可选。如果行的状态status
不是 '执行中',则认为该行可选。- 如果行不可选,而且
loadingSelection
集合中已经存在该行的id
,则将其从loadingSelection
中删除,表示加载完成。- 如果行可选,将其
id
添加到loadingSelection
集合中,表示正在加载中,并返回false
表示不可选;否则,返回true
表示可选。
handleSelectionAll
方法:
- 该方法用于处理全选操作。接收参数
val
,即当前页选中的所有行数据。- 如果有选中的行,遍历
listId
,将不在multipleSelection
中且不在loadingSelection
中的项添加到multipleSelection
中。- 如果没有选中的行,遍历
listId
,将在multipleSelection
中的项从中移除。
这些修改主要增加了对行的可选性的判断,以及对加载状态的管理,通过 loadingSelection
集合来标记哪些行正在加载中。这样可以更好地控制在某些条件下禁止选择或在加载中时保持选择状态。