艺灵设计

全部文章
×

bug复盘之如何优雅的实现表格跨页勾选

作者:艺灵设计 - 来源:http://www.yilingsj.com - 发布时间:2023-02-27 21:06:26 - 阅: - 评:0 - 积分:0

摘要:在过去一年多的时间里,“表格跨页勾选”这个功能一直困扰着我们。在多个项目里均出现不同程度的bug,我也因考虑不全踩过几次坑,最终将其成功解决。回头再看这个功能,其实挺常用的,但如果方法不对,还是会走不少弯路的。

一、需求回顾

先来简单描述下该需求在项目中的一处应用吧。场景如下:“有一个免邮范围的单选功能,选项一是全部商品参加,选项二是部分商品参加。当勾选部分商品时会出现一个'选择商品'的按钮,点击此按钮会有一个Dialog弹窗,在弹窗内用户可手动勾选商品......”

默认效果见下图↓↓↓默认选项为全部商品参加

为了让看官更好的理解这个需求,我打算放一组步骤图加以说明。

当用户点击“部分商品参加”选项,会出现“选择商品”按钮。见图二↓↓↓勾选部分商品参加后的效果

用户点击“选择商品”按钮后,会出现一个Dialog弹窗。此时用户可以勾选自己想要参加的商品,也可以点击分页来进行选择。见图三↓↓↓在弹窗中勾选商品

当用户选择完点击了“确定”按钮后,弹窗关闭,选中的数据会显示在页面上。见图四↓↓↓选择商品后会显示在页面上

如果用户想继续选择商品,点击“选择商品”按钮,会重复以上步骤。

需求已经说完了,不知道各位看官有什么想法。可能很多看官会觉得这是一个很普通、很常见的功能。

这么说也没毛病,但实现起来并不容易啊!因为这个里面存在一个歧义!

1.1、歧义:用户选择了几条数据?

主要产生歧义的地方是图三,我们来模拟一下步骤。

  1. 在第1页时,用户勾选了前3条数据。
  2. 切到第2页后,用户勾选了前2条数据。
  3. 现在点“确定”按钮,请问用户选择了几条数据?

步骤如下图↓↓↓用户选择了几条数据

一种答案是:5条,还有一种答案是:2条

回答5条是因为第1页勾选了3条,切到第2页时又勾选了2条,3+2=5。

回答2条是因为用户点确定时停留在了第2页,而第2页仅勾选了2条。

这就是问题所在。

如果这个歧义不解决,我们所做的功能就是有问题的。而且类似功能做的越多,最后为之修改而付出的代价就越大!!

1.2、影响很大!

如实说,这个功能在公司项目里大量的出现!有的地方是选择商品,有的地方是选择会员,有的地方是选择用户,有的地方是选择优惠券......

可能当时的开发者也没有注意到这个歧义,直接选择了后一种。产品在设计之初也没有标明此处的逻辑,再加上前期的测试遗漏,又没有什么客户反馈这个问题等多方面因素,大家就没有在意这件事情。

直到后面项目越复制越多,类似功能越来越多时,问题终于暴露了出来!而想要纠正这个错误所要花费的时间和工作量是巨大的!!毕竟一个功能写好后,类似功能都是习惯性Ctrl+C,接着Ctrl+V,修修改改能跑就行,没有多少人会愿意花费一些时间来重新写一遍逻辑......

二、reserve-selection + row-key + toggleRowSelection能解决勾选问题吗?

2.1、如何实现表格的勾选功能?

我们项目中用的是Element-UIel-table表格组件。官网文档→→→Table 表格

通过官方文档可知:给<el-table-column>添加一个type="selection"属性即可实现最左边的勾选功能,配合selection-change事件即可监听到当前页的勾选数据。

注意一下刚才的黄色高亮加粗文字,此时我们只能监听到当前页的勾选。这也就解释了前面点确定时,为什么保存的是2条数据了。动态gif如下图↓↓↓selection-change只能得到当前页的勾选数据

2.1.1、demo演示

此处提供一个演示Demo,有兴趣的看官可以点击绿色的vConsole按钮或右上角的“新窗口预览”,按下F12,一边操作一边看控制台日志哈。

2.2、如何实现表格的跨页勾选功能?

很显然,现在的条件无法让我们获取非当前页勾选的数据。

不要慌,好在Element-UI又提供了row-keyreserve-selection这2个属性,官方描述如下:

仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)

是不是看到希望了?如果看官亲自试一下的话,发现在切换分页后,确实是可以监听到所有页的勾选数据。动态gif图如下↓↓↓添加reserve-selection属性后可获取总勾选数据

2.2.1、demo演示

此处提供一个演示Demo,有兴趣的看官可以点击绿色的vConsole按钮或右上角的“新窗口预览”,按下F12,一边操作一边看控制台日志哈。

通过上面的Demo可以发现,目前确实是可以获取到表格的勾选总数据的,也不用担心分页的问题。

但这个真的靠谱吗???

之前我也是拿它当解决问题的首选方案。但我越测试越发现,如果需求中存在可编辑的情况,那此时完全就是一个坑啊!!!因为我无法准确知道用户勾选了哪些数据!

可能很多看官看到这里时会有些疑惑。别急,艺灵我提供一个可复现问题的步骤,有兴趣的看官可以在下面的demo中进行验证。

2.2.2、demo演示

此处提供一个演示Demo,有兴趣的看官可以点击绿色的vConsole按钮或右上角的“新窗口预览”,按下F12,一边操作一边看控制台日志哈。

三、select + select-all + toggleRowSelection才是正解

我也是走了很多弯路和看了很多遍官方文档才发现,原来解决方法是这么的简单!

3.1、select的用法

先来说select,官方解释为:“当用户手动勾选数据行的 Checkbox 时触发的事件”。形参有两个,一个是selection(已勾选的数据),另一个是row(当前行数据)。更详细说明见官方文档→→→select

也就是说,仅通过select事件,我们就能知道表格中勾选的数据,完全不用考虑分页的问题。

这么简单???

是的!

原理分析:Checkbox的特点是可表示两种状态间的切换。

再说直白点儿,就是在勾选状态下点击会取消勾选,在取消状态下点击会勾选。

假设我们定义一个变量cacheChecked来存储勾选的数据。用户每次点击复选框时,我们只需要拿rowcacheChecked做个对比便能知道用户是要进行勾选操作还是取消操作。相关代码见下方。

<!--
 * @Description: 利用select来监听勾选的数据
 * @Author: yiling (315800015@qq.com)
 * @Date: 2023-01-29 16:26:05
 * @LastEditors: yiling (315800015@qq.com)
 * @LastEditTime: 2023-01-29 16:39:33
 * @FilePath: \vue2\src\components\table-select\index.vue
-->
<template>
  <div>
    <el-table :data="tableData" ref="refElTable" style="width: 100%" @select="handleSelect">
      <el-table-column type="selection"></el-table-column>
      <el-table-column prop="name" label="商品名"></el-table-column>
      <!--省略若干代码-->
    </el-table>
    <el-pagination
      layout="total, prev, pager, next, jumper"
      :total="total"
      :page-size="limit"
      background
      v-if="total > 10"
      @current-change="handleCurrentChange"
    >
    </el-pagination>
  </div>
</template>

<script>
export default {
  data() {
    return {
      page: 1, // 当前页数
      limit: 5, // 每页条数
      total: null, // 总页数
      tableData: [], // 表格数据
      cacheChecked: [], // 临时勾选的数据
    }
  },
  computed: {
    // 计算勾选的id
    checkedIds() {
      return this.cacheChecked.map((item) => item.id)
    },
  }, // 计算机属性 类似与data概念
  created() {
    this.total = 25
    this.handleCurrentChange() // 默认加载第1页
  }, // 生命周期-创建完成(可以访问当前this实例)
  methods: {
    /**
     * @author: yiling (315800015@qq.com)
     * @description: 监听勾选每行的数据
     * @param {Array} selection 当前页选中的数据(此处无用)
     * @param {Object} row 当前行数据
     * @return {*}
     * @Date: 2023-01-16 09:57:38
     */
    handleSelect(selection, row) {
      // 说明已选中,现在要取消 ↓↓↓
      if (this.checkedIds.includes(row.id)) {
        console.log('要取消的id=', row.id)
        // 遍历已选中的数据 ↓↓↓
        this.cacheChecked.filter((item, index) => {
          // 找到了当前行数据 ↓↓↓
          if (item.id === row.id) {
            this.cacheChecked.splice(index, 1) // 删除掉
          }
        })
      } else {
        console.log('要插入的id=', row.id)
        this.cacheChecked.push(row) // 当前行数据不在缓存记录中,追加进来
      }
      console.log('已勾选的数据=', this.cacheChecked)
    },
    /**
     * @author: yiling (315800015@qq.com)
     * @description: mock假数据
     * @param {*}
     * @return {*}
     * @Date: 2023-01-16 10:10:48
     */
    mockData() {
      const tableData = []
      for (let i = 1; i <= this.limit; i++) {
        const id = (this.page - 1) * this.limit + i
        tableData.push({ id: id, name: '商品' + id })
      }
      this.tableData = tableData
    },
    /**
     * @author: yiling (315800015@qq.com)
     * @description: 切换分页
     * @param {Number} page 当前页数
     * @return {*}
     * @Date: 2023-01-16 11:07:15
     */
    handleCurrentChange(page = 1) {
      console.log('当前页数=', page, ';已勾选的数据=', this.cacheChecked)
      this.page = page
      this.mockData()
    },
  }, // 挂载一些方法
}
</script>

我们用了一个select就已经实现了70%的功能,离成功已经很近了。

但现在有一个很尴尬的问题。

什么问题呢?那就是切回已勾选的那一页时,勾选状态不见了,但数据还在!动态gif图如下↓↓↓切换分页后勾选状态不见了但数据还在

从上图中可以看到,在第1页勾选数据后,控制台有打印。切到第2页并勾选后,日志中显示已勾选数据为2条,现在是没问题的。当我们再切回第1页时,之前选中的那个勾选状态不见了,但控制台中仍显示勾选2条数据。

3.2、回填勾选数据

刚才之所以尴尬是因为在切换分页后,我们没有动态去回填商品的勾选状态

说人话就是:切换分页后,已勾选的状态要保留

既然要回填勾选数据,那首先得知道在什么时候才需要回填。一共有三种情况,分析如下:

  1. 点击“选择商品”按钮后,弹窗打开
  2. 在弹窗中切换分页
  3. 从列表页点编辑进入详情页时

分析完毕,接下来又要上代码了。

3.2.1、情况一:弹窗打开

点击“选择商品”后会打开Dialog弹窗,此时需要进行一次回填。为了实现回填功能,需要做以下2件事情:

  1. 重置勾选的数据;
  2. 请求第1页的数据

相关代码见下方↓↓↓

<script>
export default {
  data() {
    return {
      ...... // 省略部分见3.1

      // 新增↓↓↓
      showTableData: [], // 最终显示的数据
    }
  },
  // 挂载一些方法 ↓↓↓
  methods: {
    /**
      * @author: yiling (315800015@qq.com)
      * @description: 点击“选择商品”按钮打开弹窗
      * @param {*}
      * @return {*}
      * @Date: 2023-01-30 11:07:15
      */
    handleDialogOpen() {
      this.dialogVisible = true // 打开弹窗

      // 1、先重置勾选的数据,防止上一次点击了“取消”或“移除”后的数据与当前勾选数据不统一
      this.cacheChecked = JSON.parse(JSON.stringify(this.showTableData))

      // 2、请求第1页数据
      this.$nextTick(() => {
        this.handleCurrentChange()
      })
    },
    /**
      * @author: yiling (315800015@qq.com)
      * @description: 点击弹窗中的确定按钮
      * @param {*}
      * @return {*}
      * @Date: 2023-01-30 11:07:15
      */
    handleDialogSubmit() {
      this.showTableData = this.cacheChecked // 把临时选中的数据赋值给最终要显示的变量
      this.dialogVisible = false // 关闭弹窗
    },
  },
}
</script>

通过以上两个步骤,我们便巧妙的在Dialog弹窗打开时恢复了第1页数据的勾选状态。

如果用户点击第2页、第3页、第n页,在handleCurrentChange()方法中调用restoreSelectionTable()即可动态恢复勾选状态。

3.2.2、情况二:切换分页

我们来修改刚才的代码,完善自定义的restoreSelectionTable()方法。修改如下↓↓↓

<!-- 利用toggleRowSelection来恢复勾选状态↓↓↓ -->
<script>
export default {
  // 挂载一些方法 ↓↓↓
  methods: {
    /**
     * @author: yiling (315800015@qq.com)
     * @description: mock假数据
     * @param {*}
     * @return {*}
     * @Date: 2023-01-16 10:10:48
     */
    mockData() {
      ...... // 省略部分见3.1
      this.tableData = tableData

      // 在请求数据后要进行回显勾选状态↓↓↓
      this.$nextTick(() => {
        if (this.cacheChecked.length) {
          this.restoreSelectionTable()
        }
      })
    },
    /**
     * @author: yiling (315800015@qq.com)
     * @description: 恢复表格数据的勾选状态
     * @param {*}
     * @return {*}
     * @Date: 2023-01-16 11:07:15
     */
    restoreSelectionTable() {
      this.tableData.forEach((item, index) => {
        const isTrue = this.checkedIds.includes(item.id) // 判断每一条数据是否勾选过
        console.log('id为 %c' + item.id, 'color:#f00;', '的商品需要勾选?', isTrue)
        this.$refs?.refElTable?.toggleRowSelection(this.tableData[index], isTrue) // 如果为true则进行勾选,否则要取消勾选
      })
    },
  },
}
</script>

上方代码的关键点是toggleRowSelection(row, selected)。这个是Element-UI提供的方法,其说明为:“用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中)”

至此,我们便成功的解决了切换分页后已勾选数据状态丢失的bug了。耶!

那是不是意味着完全解决了问题呢?

目前还有一点儿小瑕疵的哈~

3.3、select-all 全选的用法

最后一个小瑕疵:勾选表头的“全选”,再点“确定”按钮,页面上无任何数据变化。bug如下图↓↓↓表头的全选功能

我们来给el-table添加@select-all事件,并在事件中做些处理即可实现全选的功能。相关代码如下:

<!-- select-all监听全选↓↓↓ -->

<!-- 修改前↓↓↓ -->
<el-table :data="tableData" ref="refElTable" style="width: 100%" @select="handleSelect">

<!-- 修改后↓↓↓ -->
<el-table :data="tableData" ref="refElTable" style="width: 100%" @select="handleSelect" @select-all="handleSelectAll">

<script>
export default {
  // 挂载一些方法 ↓↓↓
  methods: {
    /**
      * @author: yiling (315800015@qq.com)
      * @description: 当用户手动勾选全选 Checkbox 时触发的事件
      * @param {Array} selection 当前页选中的数据
      * @return {*}
      * @Date: 2023-02-20 17:13:12
      */
    handleSelectAll(selection) {
      console.log('手动全选', selection)
      // 勾选全选时
      if (selection.length) {
        // 1、遍历当前页选中的数据
        selection.filter((item) => {
          // 2、如果当前id不在缓存记录中
          if (!this.checkedIds.includes(item.id)) {
            this.cacheChecked.push(item) // 3、则追加进去
            console.log('勾选全选时,要选中的id=',item.id)
          }
        })
      } else {
        // 取消勾选时
        const ids = this.tableData.map((item) => item.id) // 1、筛选出当前面的id
        // 2、遍历缓存中选中的数据
        this.cacheChecked.filter((item, index) => {
          // 3、如果当前页匹配到缓存中的id
          if (ids.includes(item.id)) {
            this.cacheChecked.splice(index,1) // 4、删除缓存中的这条记录
            console.log('勾选全选时,要移除选中的id=', item.id)
          }
        })
      }
    },
  },
}
</script>

上方代码中的handleSelectAll全选事件里面做了很多事情,主要分为2种情况。一种是勾选全选,一种是取消全选。针对两种不同的情况,又需要判断缓存中的数据是要删除还是要追加。具体的情况可以看上方代码中的注释,此处不再赘述。

3.3.1、demo演示

现在我们来看一下最终的Demo,有兴趣的看官可以点击绿色的vConsole按钮或右上角的“新窗口预览”,按下F12,一边操作一边看控制台日志哈。

至此,我们终于解决了这个“看上去简单做起来复杂“的表格跨页勾选难题!真是不容易啊!

四、结束语

在工作中,我们会遇到很多像文章中提及的这种看似简单但实现起来又没有那么简单的功能。这个时候只有多考虑一些情况,我们的代码才会更加健壮一些。

转载声明:
  若亲想转载本文到其它平台,请务必保留本文出处!
本文链接:/xwzj/2023-02-27/table-reserve-selection.html

若亲不想直保留地址,含蓄保留也行。艺灵不想再看到有人拿我的技术文章到他的地盘或者是其它平台做教(装)程(B)而不留下我的痕迹。文章你可以随便转载,随便修改,但请尊重艺灵的劳动成果!谢谢理解。

亲,扫个码支持一下艺灵呗~
如果您觉得本文的内容对您有所帮助,您可以用支付宝打赏下艺灵哦!

Tag: bug table 表格组件 Vue2 Element-UI reserve-selection

上一篇: 记一次APP内调起微信支付时首次成功唤醒,后面全是[payment微信:-1]的排查经历   下一篇: 浅聊某sdn是如何实现未登录时屏蔽复制的

评论区