Vue使用el-table实现表格跨页多选
作者:前端手术刀
前言
在我们日常项目开发中,经常会有 表格跨页多选
的需求,接下来让我们用 el-table
示例一步步来实现这个需求。
动手开发
在线体验
priceless-mcclintock-4cp7x3 - CodeSandbox
常规版本
本部分只写了一些重点代码,心急的彦祖可以直接看 性能进阶版
- 首先我们需要初始化一个选中的数组
checkedRows
this.checkedRows = []
- 在触发选中的时候,我们就需要把当前行数据
push
到checkedRows
,否则就需要剔除对应行
<el-table ref="multipleTable" @select="handleSelectChange"> handleSelectChange (val, row) { const checkedIndex = this.checkedRows.findIndex(_row => _row.id === row.id) if (checkedIndex > -1) { // 选中剔除 this.checkedRows.splice(checkedIndex, 1) } else { // 未选中压入 this.checkedRows.push(row) } }
- 实现换页的时候的回显逻辑
this.data.forEach(row=>{ const checkedIndex = this.checkedRows.findIndex(_row => _row.id === row.id) if(checkedIndex>-1) this.$refs.multipleTable.toggleRowSelection(row,true) })
效果预览
让我们看下此时的效果
完整代码
<template> <div> <el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%" @select="handleSelectChange" @select-all="handleSelectAllChange" > <el-table-column type="selection" width="55" /> <el-table-column label="日期" width="120" prop="date" /> <el-table-column prop="name" label="姓名" width="120" /> </el-table> <el-pagination background :current-page.sync="currentPage" layout="prev, pager, next" :total="1000" @current-change="currentChange" /> </div> </template> <script> export default { data () { return { currentPage: 1, checkedRows: [], pageSize: 10, totalData: Array.from({ length: 1000 }, (_, index) => { return { date: '2016-05-03', id: index, name: '王小虎' + index } }) } }, computed: { tableData () { const { currentPage, totalData, pageSize } = this return totalData.slice((currentPage - 1) * pageSize, currentPage * pageSize) } }, methods: { currentChange (page) { this.currentPage = page this.tableData.forEach(row => { const checkedIndex = this.checkedRows.findIndex(_row => _row.id === row.id) if (checkedIndex > -1) this.$refs.multipleTable.toggleRowSelection(row, true) }) }, handleSelectChange (val, row) { const checkedIndex = this.checkedRows.findIndex(_row => _row.id === row.id) if (checkedIndex > -1) { this.checkedRows.splice(checkedIndex, 1) } else { this.checkedRows.push(row) } }, handleSelectAllChange (val) { this.tableData.forEach(row => { this.handleSelectChange(null, row) }) } } } </script>
性能进阶版
性能缺陷分析
优秀的彦祖们,应该发现以上代码的性能缺陷了
1. handleSelectChange
需要执行一个 O(n)
复杂度的循环
2. currentChange
的回显逻辑内部, 有一个 O(n^2)
复杂度的循环
想象一下 如果场景中勾选的行数达到了 10000
行, 每页显示 100
条
那么我们每次点击换页 最坏情况就要执行 10000 * 100
次循环,这是件可怕的事...
重新设计数据结构
其实我们没必要把 checkedRows
设计成一个数组, 我们可以设计成一个 map
,这样读取值就只需要 O(1)
复杂度
1.改造 checkedRows
this.crossPageMap = new Map()
2.修改选中逻辑( 核心代码
)
handleSelectChange (val, row) { // 实现了 O(n) 到 O(1) 的提升 const checked = this.crossPageMap.has(row.id) if (checked) { this.crossPageMap.delete(row.id) } else { this.crossPageMap.set(row.id, row) } }
3.修改换页回显逻辑
currentChange (page) { this.currentPage = page // 实现了 O(n^2) 到 O(n) 的提升 this.tableData.forEach(row => { const checked = this.crossPageMap.has(row.id) if (checked) this.$refs.multipleTable.toggleRowSelection(row, true) }) }
完整代码
<template> <div> <el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%;height:500px" @select="handleSelectChange" @select-all="handleSelectAllChange" > <el-table-column type="selection" width="55" /> <el-table-column label="日期" width="120" prop="date" /> <el-table-column prop="name" label="姓名" width="120" /> </el-table> <el-pagination background :current-page.sync="currentPage" layout="prev, pager, next" :total="1000" @current-change="currentChange" /> </div> </template> <script> export default { data () { return { currentPage: 1, crossPageMap: new Map(), pageSize: 10, totalData: Array.from({ length: 1000 }, (_, index) => { return { date: '2016-05-03', id: index, name: '王小虎' + index } }) } }, computed: { tableData () { const { currentPage, totalData, pageSize } = this return totalData.slice((currentPage - 1) * pageSize, currentPage * pageSize) } }, methods: { currentChange (page) { this.currentPage = page this.tableData.forEach(row => { const checked = this.crossPageMap.has(row.id) if (checked) this.$refs.multipleTable.toggleRowSelection(row, true) }) }, handleSelectChange (val, row) { const checked = this.crossPageMap.has(row.id) if (checked) { this.crossPageMap.delete(row.id) } else { this.crossPageMap.set(row.id, row) } }, handleSelectAllChange (val) { this.tableData.forEach(row => { this.handleSelectChange(null, row) }) } } } </script>
抽象业务逻辑
以上就是完整的业务代码部分,但是为了复用性。
我们考虑可以把其中的逻辑抽象成一个 CrossPage
类
设计 CrossPage 类
接收以下参数
`data` - 行数据 `key` - 行数据唯一值 `max` - 最大选中行数 `toggleRowSelection` - 切换行数据选中/取消选中的方法
提供以下方法
`onRowSelectChange` - 外部点行数据点击的时候调用此方法 `onDataChange` - 外部数据变化的时候调用此方法 `clear` - 清空所有选中行
构造器大致代码 如下
constructor (options={}) { this.crossPageMap = new Map() this.key = options.key || 'id' this.data = options.data || [] this.max = options.max || Number.MAX_SAFE_INTEGER this.toggleRowSelection = options.toggleRowSelection if(typeof this.toggleRowSelection !== 'function') throw new Error('toggleRowSelection is not function') }
设置私有crossPageMap
彦祖们,问题来了,我们把 crossPageMap
挂载到实例上,那么外部就可以直接访问修改这个变量。
这可能导致我们内部的数据逻辑错乱,所以必须禁止外部访问。
我们可以使用 #
修饰符来实现私有属性,具体参考
类私有域 - JavaScript | MDN (mozilla.org)
完整代码
- CrossPage.js
/** * @description 跨页选择 * @param {Object} options * @param {String} options.key 行数据唯一标识 * @param {Array} options.data 行数据 * @param {Number} options.max 最大勾选行数 * @param {Function} options.toggleRowSelection 设置行数据选中/取消选中的方法,必传 */ export const CrossPage = class { #crossPageMap = new Map(); constructor (options={}) { this.key = options.key || 'id' this.data = options.data || [] this.max = options.max || Number.MAX_SAFE_INTEGER this.toggleRowSelection = options.toggleRowSelection if(typeof this.toggleRowSelection !== 'function') throw new Error('toggleRowSelection is not function') } get keys(){ return Array.from(this.#crossPageMap.keys()) } get values(){ return Array.from(this.#crossPageMap.values()) } get size(){ return this.#crossPageMap.size } clear(){ this.#crossPageMap.clear() this.updateViews() } onRowSelectChange (row) { if(typeof row !== 'object') return console.error('row is not object') const {key,toggleRowSelection} = this const checked = this.#crossPageMap.has(row[key]) if(checked) this.#crossPageMap.delete(row[key]) else { this.#crossPageMap.set(row[key],row) if(this.size>this.max){ this.#crossPageMap.delete(row[key]) toggleRowSelection(row,false) } } } onDataChange(list){ this.data = list this.updateViews() } updateViews(){ const {data,toggleRowSelection,key} = this data.forEach(row=>{ toggleRowSelection(row,this.#crossPageMap.has(row[key])) }) } }
- crossPage.vue
<template> <div> <el-table ref="multipleTable" :data="tableData" tooltip-effect="dark" style="width: 100%" @select="handleSelectChange" @select-all="handleSelectAllChange" > <el-table-column type="selection" width="55" /> <el-table-column label="日期" width="120" prop="date" /> <el-table-column prop="name" label="姓名" width="120" /> </el-table> <el-button @click="clear"> 清空 </el-button> <el-button @click="keys"> 获取 keys </el-button> <el-button @click="values"> 获取 values </el-button> <el-pagination background :current-page.sync="currentPage" layout="prev, pager, next" :total="1000" @current-change="currentChange" /> </div> </template> <script> import { CrossPage } from './CrossPage' export default { data () { return { currentPage: 1, pageSize: 10, totalData: Array.from({ length: 1000 }, (_, index) => { return { date: '2016-05-03', id: index, name: '王小虎' + index } }), multipleSelection: [] } }, computed: { tableData () { const { currentPage, totalData, pageSize } = this return totalData.slice((currentPage - 1) * pageSize, currentPage * pageSize) } }, mounted () { this.crossPageIns = new CrossPage({ key: 'id', max: 2, data: this.tableData, toggleRowSelection: this.$refs.multipleTable.toggleRowSelection }) }, methods: { clear () { this.crossPageIns.clear() }, keys () { console.log('keys:', this.crossPageIns.keys) }, values () { console.log('values:', this.crossPageIns.values) }, currentChange (page) { this.currentPage = page // 调用实例 onDataChange 方法 this.crossPageIns.onDataChange(this.tableData) }, handleSelectChange (val, row) { // 调用实例 onRowSelectChange 方法 this.crossPageIns.onRowSelectChange(row) }, handleSelectAllChange (val) { this.tableData.forEach(row => { this.crossPageIns.onRowSelectChange(row) }) } } } </script>
写在最后
未来想做的还有很多
- 利用
requestIdleCallback
提升单页大量数据的toggleRowSelection
渲染效率 - 提供默认选中项的配置
- ...
以上就是Vue使用el-table实现表格跨页多选的详细内容,更多关于Vue el-table表格跨页多选的资料请关注脚本之家其它相关文章!