Element-ui table cross-page multi-selection implementation

foreword

In our daily project development, we often have 表格跨页多选requirements. Next, let us use el-tableexamples to realize this requirement step by step.

hands-on development

online experience

https://codesandbox.io/s/priceless-mcclintock-4cp7x3?file=/src/App.vue

regular version

This part only writes some key codes, impatient Yanzu can directly read性能进阶版

  1. First we need to initialize a selected arraycheckedRows
this.checkedRows = []
  1. When triggering the selection, we need to get the data of the current row push, checkedRowsotherwise we need to delete the corresponding row
<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. Echo logic when implementing page change
this.data.forEach(row=>{
    
    
  const checkedIndex = this.checkedRows.findIndex(_row => _row.id === row.id)
  if(checkedIndex>-1) this.$refs.multipleTable.toggleRowSelection(row,true)
})

Effect preview

Let's see the effect at this time

2023-08-08 20.03.52.gif

full code

<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>

Performance Advanced Edition

performance defect analysis

Excellent Yan ancestors, you should find the performance defects of the above code

1. handleSelectChangeNeed to execute a O(n)complexity cycle

2. currentChangeInside the echo logic, there is a O(n^2)complexity loop

Imagine if the number of checked lines in the scene reaches 10000lines, each page displays 100items

Then we have to execute worst- 10000 * 100case loops every time we click to change pages, which is a terrible thing...

Redesign the data structure

In fact, we don't need to checkedRowsdesign it as an array, we can design it as one map, so that reading the value only requires O(1)complexity

1. TransformationcheckedRows

this.crossPageMap = new Map()

2. Modify the selection logic ( 核心代码)

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. Modify the page change echo logic

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)
  })
}

full code

<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>

abstract business logic

The above is the complete business code part, but for reusability.

We consider that the logic can be abstracted into a CrossPageclass

Design the CrossPage class

Receive the following parameters

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

Provides the following methods

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

The general code of the constructor is as follows

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')
}

Set private crossPageMap

Yan ancestors, the problem is coming, we mount it crossPageMapon the instance, then the outside can directly access and modify this variable.

This may lead to confusion in our internal data logic, so external access must be prohibited.

We can use #modifiers to implement private properties, see for details

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/Private_class_fields

full code

  • 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>

write at the end

There are still many things I want to do in the future

  • Use to improve the rendering efficiency requestIdleCallbackof a large amount of data on a single pagetoggleRowSelection
  • Provide configuration for default selected items

Guess you like

Origin blog.csdn.net/weixin_45506717/article/details/132213179
Recommended