什么,你还不会 vue 表格跨页多选?

在我们日常项目开发中,经常会有表格跨页多选的需求,接下来让我们用 el-table 示例一步步来实现这个需求。

动手开发

在线体验

codesandbox.io/s/priceless…

常规版本

本部分只写了一些重点代码,心急的彦祖可以直接看 性能进阶版

  1. 首先我们需要初始化一个选中的数组 checkedRows
this.checkedRows = []
 
  1. 在触发选中的时候,我们就需要把当前行数据 pushcheckedRows,否则就需要剔除对应行
<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)
  }
}


 

  1. 实现换页的时候的回显逻辑
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)复杂度

Object 和 Map 的选择

此时应该有 彦祖会好奇,为什么要搞一个 Map 而不是 Object呢?

其实要弄清楚这个问题,我们必须要知道他们之间的区别,网上的文章非常多,也介绍的非常详细

但有一点,是很多文章没有提及的,那就是 Map 是有序的,Object 是无序的

比如有个需求要获取 第一个选中行,最后一个选中行,那么我们利用 Map 实现就非常简单。

其次 我们可以用 size 方法轻松获取 选中行数量

改造代码

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 => {
        const isChecked = this.crossPageIns.isChecked(row)
        if (val.length === 0) {
          // 取消全选 只有选中的需要改变状态
          if (isChecked) this.crossPageIns.onRowSelectChange(row)
        } else {
          // 全选 只有未选中的才需要改变状态
          if (!isChecked) this.crossPageIns.onRowSelectChange(row)
        }
      })
    }
  }
}
</script>

抽象业务逻辑

以上就是完整的业务代码部分,但是为了复用性。

我们考虑可以把其中的逻辑抽象成一个CrossPage

设计 CrossPage 类

接收以下参数

`data` - 行数据
`key` - 行数据唯一值
`max` - 最大选中行数
`toggleRowSelection` - 切换行数据选中/取消选中的方法

提供以下方法

`onRowSelectChange` - 外部点行数据点击的时候调用此方法
`onDataChange` - 外部数据变化的时候调用此方法
`clear` - 清空所有选中行
`isChecked` - 判断当前行是否选中

构造器大致代码 如下

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挂载到实例上,那么外部就可以直接访问修改这个变量。

这可能导致我们内部的数据逻辑错乱,所以必须禁止外部访问。

我们可以使用 # 修饰符来实现私有属性,具体参考

developer.mozilla.org/zh-CN/docs/…

完整代码

  • CrossPage.js
/**
 * @description 跨页选择
 * @param {Object} options
 * @param {String} options.key 行数据唯一标识
 * @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()
  }
  isChecked(row){
    return this.#crossPageMap.has(row[this.key])
  }
  onRowSelectChange (row) {
    if(typeof row !== 'object') return console.error('row is not object')
    const {key,toggleRowSelection} = this
    if(this.isChecked(row)) 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.isChecked(row))
    })
  }
}

 

写在最后

未来想做的还有很多

  • 利用requestIdleCallback 提升单页大量数据的 toggleRowSelection 渲染效率
  • 提供默认选中项的配置
  • ...

欢迎彦祖们 贡献宝贵代码

个人能力有限 如有不对,欢迎指正🌟 如有帮助,建议小心心大拇指三连🌟


补给资料    管注公众号:码农补给站     


原文链接:https://juejin.cn/post/7264898713646153780
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue实现表格下拉多选筛选的方法有许多种,以下是一种可能的实现方式: 1. 首先,在Vue组件中定义一个表格数据源的数组(比如dataList)和一个保存筛选条件的数组(比如selectedOptions)。 2. 在模板中,使用`<select>`标签创建一个下拉多选框,并通过`v-model`指令将选中的选项绑定到selectedOptions数组上。 3. 使用`<table>`标签创建一个表格,并通过`v-for`指令遍历dataList数组,将每一行数据显示在表格中。 4. 在表格的头部或者其他合适的位置,添加一个“筛选”按钮,并为其绑定一个点击事件(比如`@click="filterData"`)。 5. 在Vue实例的methods中,定义filterData方法,在该方法内部根据selectedOptions数组中的选项对dataList数组进行筛选操作,生成一个新的数组(比如filteredData)。 6. 将filteredData数组赋值给表格数据源的数组dataList,即可实现表格的筛选效果,页面会根据选中的筛选条件实时更新。 具体代码示例: ```html <template> <div> <select multiple v-model="selectedOptions"> <option v-for="option in options" :value="option">{{ option }}</option> </select> <button @click="filterData">筛选</button> <table> <thead> <tr> <th>Name</th> <th>Age</th> <th>Email</th> </tr> </thead> <tbody> <tr v-for="item in dataList" :key="item.id"> <td>{{ item.name }}</td> <td>{{ item.age }}</td> <td>{{ item.email }}</td> </tr> </tbody> </table> </div> </template> <script> export default { data() { return { options: ['Option 1', 'Option 2', 'Option 3'], // 下拉多选框的选项 selectedOptions: [], // 选中的选项 dataList: [ // 表格数据源 { id: 1, name: 'John', age: 30, email: '[email protected]' }, { id: 2, name: 'Alice', age: 25, email: '[email protected]' }, { id: 3, name: 'Bob', age: 35, email: '[email protected]' } ] }; }, methods: { filterData() { // 根据选中的选项对数据源进行筛选 this.dataList = this.dataList.filter(item => this.selectedOptions.includes(item.name)); } } }; </script> ``` 以上是一种简单的实现方式,可以根据实际需求进行修改和扩展。希望对你有帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值