1.前言

在日常业务开发中,下拉框是一个非常常用的组件,使用起来也非常的简单,只需要引入组件并配置数据源即可完成基础功能的使用。

<a-select
  :disabled='disabled'
  v-model='model.idList'
  placeholder='请选择'
  mode='multiple'
>
  <a-select-option v-for='item in dataList' :key='item.id' :value='item.id' :label='item.name'>
     {{ item.name }}
  </a-select-option>
</a-select>

2.问题浮现

本来一切正常的使用着a-select,但就在今天,有个下拉框返回的数据量过大,导致下拉框加载非常的卡顿,于是需要将该下拉框改为了分页查询,每次查询20条数据,滚动到底部时再查询20条,以此类推实现滚动分页,这个功能并不难实现。只需要在组件的滚动监听上绑定触底查询方法即可。

<a-select
  @popupScroll="scrollEvent"   --滚动监听,滚动到底部时查询下一页数据
  @search='searchEvent'        --输入监听,用于条件查询
  @blur='blurEvent'            --失焦监听,输入搜索值后直接失焦需要清空查询条件,否则查询数据会残留
  :disabled='disabled'
  v-model='model.idList'
  placeholder='请选择'
  mode='multiple'
>
  <a-select-option v-for='item in dataList' :key='item.id' :value='item.id' :label='item.name'>
     {{ item.name }}
  </a-select-option>
</a-select>

/**
 * @author BoJack
 * @description 项目下拉框滚动事件
 * @date 2025/3/27 20:00
 * @param event 滚动事件
 **/
scrollEvent(event) {
  const { target } = event
  const scrollHeight = target.scrollHeight - target.scrollTop
  const clientHeight = target.clientHeight
  console.log('滚动条', scrollHeight, clientHeight)
  // 判断下拉框滚动条到达底部
  if (scrollHeight == clientHeight) {
    //调用查询接口,scrollParam存放分页数据,此处无需再传入搜索值,若搜索框有值的话,滚动查询时会自动带上该搜索值
    this.pageSelectList(this.scrollParam)
  }
},

/**
 * @description 分页查询列表下拉框
 * @param scrollParam 分页参数
 * @param searchValue 搜索值
 **/
pageSelectList(scrollParam, searchValue) {
  //搜索值
  scrollParam.searchValue = searchValue
  getAction(this.url.pageList, scrollParam).then((res) => {
    if (res.success) {
      this.dataList = this.dataList.concat(res.result)
      //查询成功后页码+1
      this.scrollParam.scrollPage++
    } else {
      this.$message.warning(res.message)
    }
  })
},

/**
 * @author BoJack
 * @description 输入文字搜索
 * @date 2025/3/28 10:07
 * @param searchValue 搜索值
 **/
searchEvent(searchValue) {
  // 查询前清空列表
  this.propertyList = []
  // 设置查询页码为1
  this.scrollParam.scrollPage = 1
  this.pageSelectList(this.scrollParam, searchValue)
},

随后回到服务端写完查询接口,保存编译运行一气呵成。到这一步,一切都是那么的顺利。回到页面上一看,发现滚动分页功能一切正常,但大数量时的回显是个问题,如果保存了一条排名60的数据,那么再次打开页面时,默认分页只查询了前20条数据,但该数据在60的位置,所以该数据依然不能正常显示(无法获取到对应name,会直接显示id在下拉框里)。

方案一

于是我想,既然需要回显该数据,那干脆回显时多查一次,查询时先单独用WHERE条件查询到该数据,将其存到list中,随后再进行分页查询,然后将两次查询的list合并后统一返回给前端,这样,下拉框里就一定会有回显的那条数据了,但是,想到这里,又有一个问题,如果按上述逻辑,先行查询回显数据,再分页查询剩余数据,那我向下滚动三次,当分页数据到达60的时候,岂不是该数据就会重复显示两次,那这样的话就只能对List进行去重,想到去重,我首先想到了Set,但关键是该List中存放的是引用数据类型(也就是Object),而非基础数据类型,所以并不能通过Set进行直接去重,恐怕只能通过循环遍历进行去重,本来查询两次数据库代码效率就已经比较低下了,再循环遍历,效率又要大打折扣,所以果断放弃了该方案。

方案二

既然查询两次数据库造成性能浪费,那不如考虑能否一次查询到回显数据和分页数据?似乎并不难实现,只需要把回显的id传入SQL里,使用OR条件同时查询到分页数据和id IN ('回显数据id')即可,这一下,性能提升不少,毕竟减少了一次数据库的查询。

SELECT id
       name,
       code
FROM pro_info pi
WHERE (pi.id IN
       ('aaaa', 'bbbb')) --此处替换为mybatis循环idList数据
   OR (pi.name = '' AND pi.code = '')  --此处为其余的查询条件
LIMIT 20; --分页20条数据

到这一步,似乎是个完美方案,但是,我又发现,虽然该查询SQL能够正常查询到数据,但因为回显数据和分页数据是同时返回的,如果回显数据排名在20以后,那LIMIT 20分页时将直接丢弃该回显数据,这很明显是不可以的,我们可以丢弃分页中的数据(反正滚动到下一页他就又出来了)但回显数据是必须要显示的,所以应该想办法把回显的数据排序到最前排,保证分页时不会被丢弃。

方案三(完美方案)

想到排序,那肯定是通过ORDER BY了,但通常情况下使用order by都是对某个字段进行排序,那该如何将指定id的数据显示到前排呢?毫无头绪。

功力不够,ai来凑,把这个需求丢给ai后,ai给了我完全没使用过的一种全新写法,完美解决了该问题。

SELECT id
       name,
       code
FROM pro_info pi
WHERE (pi.id IN
       ('aaaa', 'bbbb'))
   OR (pi.name = '' AND pi.code = '')
ORDER BY CASE
        WHEN pi.base_id IN ('aaaa', 'bbbb') THEN 0
        ELSE 1
    END,
    pi.code
LIMIT 20;

通过CASE WHEN 表达式在这里的作用是为每条记录生成一个排序值。具体逻辑为:

如果 pi.id 在指定的列表中(即 'aaaa''bbbb')则返回 0。否则,返回 1ORDER BY 子句首先根据 CASE WHEN 表达式的结果进行排序。由于 0 小于 1,因此返回 0 的记录(即 id 在指定List中的记录)会被优先排序到结果集的前面。

从而实现指定id显示在前面,其余数据按code进行次要排序。

总结

出现后端大数据返回给前端时,可以通过分页的方式来解决大数据量的卡顿,但在对下拉框组件、树形组件或其他滚动型的组件进行分页时,应注意已有数据回显,查询时应当优先显示该数据,否则会被LIMIT 截断导致回显数据无法正常显示。而通过 CASE WHEN 表达式对指定id进行排序可以很好的解决这一问题。

感谢你看到这里,希望这篇文章能够帮到你,如果你有更好的解决方案,欢迎到评论区留言!

Make Java Great Again!