基于 vue 和 element-ui 实现让全系统表格支持列筛选功能

1. 项目背景

最近产品那边来了个需求,业务同事在截图汇报时希望能一张图截完全部必要的字段,本身要截图的字段并不多,但是由于有时候表格列过多,导致某些字段需要滚动到后面才能看到,因此希望在截图时只展示表格必要的那几列,其它不需要的列隐藏。

产品最初的需求是针对某个业务高频调整的特定的表格进行改动,在表格右上角添加一个图标,点击后弹出浮层可以勾选需要哪些列,就像下面这样:

而为了通用,我们最终决定做成全局的功能。

2. 功能设计

我们的项目是 Vue 2.7 + ElementUI ,历经了多年多人的维护,项目非常庞大,如果去每个表格改动代码必然是不现实的,因此需要考虑一种比较通用的方式来实现。

最容易想到的是通过 DOM API 来实现,通过手动创建一个元素添加在表格右上角,监听该元素的事件并操作表格 DOM 从而达到视觉上的表格过滤效果,但手动去修改表格列的这种方式并不优雅,而且容易出错 ------ 因为 ElementUI 表格并不是一个单纯的表格实现,而是根据参数不同而叠加多个表格来实现功能的,例如当你打开浏览器 DevTools 查看时,你可以看到表头是一个 <table>,表身是一个 <table>,如果有固定列,固定列又是单独的 <table> 元素,且需要考虑的因素过多(例如移除 DOM 后如果再次勾选需要还原等),排除此方式。

既然是 MVVM 框架,我们肯定是希望通过数据来驱动视图发生变化,如果能这样,那就相当于把维护 DOM 结构的事情交还给了 ElementUI,我们不用再考虑 DOM 的各种结构差异和一系列的问题。

因此我决定通过 Vue Mixin 的方式来实现。

为了实现上面的效果,我们需要做以下几个事情:

  1. 创建一个用于勾选列的弹出层,暂且称它为 FilterBar
  2. 表格挂载到 DOM 的时候,将 FilterBar 挂载到表格右上角
  3. 将列数据传递给 FilterBar,并监听 FilterBar 抛出的事件
  4. 根据 FilterBar 传递的数据,显示或隐藏表格列

3. 实现

我们创建一个 mixins/FilterBar 目录用于存放该功能的代码,整理目录结构如下:

其中:

  • FilterBar.vue 用于弹出层的组件实现
  • index.js 用于实现该 mixin 的主要功能
  • style.less 用于简单的样式管理

3.1 创建 FilterBar

先基于 el-popover 创建一个非常简单的 FilterBar 组件,主要用于展示列选项并支持勾选,每当勾选发生变化时就向外抛出 filter 事件通知外部。

jsx 复制代码
<template>
  <div>
    <el-popover
      placement="bottom"
      :append-to-body="true"
      v-model="searchFromPopoverVisible"
      trigger="click"
    >
      <i
        class="el-icon-menu"
        slot="reference"
      />
      <div>
        <el-checkbox-group v-model="selectedColumns">
          <el-checkbox
            v-for="column in columns"
            :label="column.label"
            :key="column.label"
            :name="column.label"
          >
            {{ column.label }}
          </el-checkbox>
        </el-checkbox-group>
      </div>
    </el-popover>
  </div>
</template>
​
<script setup>
import { ref, watch } from "vue";
​
const emit = defineEmits(["filter"]);
​
const searchFromPopoverVisible = ref(false);
const columns = ref([]);
const selectedColumns = ref([]);
​
watch(selectedColumns, (newVal) => emit("filter", newVal));
​
defineExpose({
  columns,
});
</script>

3.2 创建 Mixin

3.2.1 创建并挂载 FilterBar

在 Mixin 逻辑中,我们首先在 mounted 时挂载 FilterBar,并在监听到 filter 事件时执行对表格列的过滤操作:

js 复制代码
import Vue from "vue";
import { Table } from "element-ui";
import FilterBar from "./FilterBar.vue";
​
const FilterBarComponent = Vue.extend(FilterBar);
​
Table.mixins?.push({
  mounted() {
    // 创建一个元素用于挂载图标
    const icon = document.createElement("div");
​
    // 创建 FilterBar 组件
    const filterBar = new FilterBarComponent();
​
    // 将表格列数据写到 FilterBar 组件中
    filterBar.columns = this.store.states._columns;
​
    // 监听过滤事件
    filterBar.$on("filter", (column) => {
      this.filterColumn(column)
    });
​
    if (this.$el) {
      // 添加组件到 el-table 内
      this.$el.appendChild(icon);
​
      filterBar.$mount(icon);
    }
  },
  methods: {
    async filterColumn(column) {
      // TODO: 过滤逻辑
    },
  },
});

3.2.2 实现过滤逻辑

要实现过滤逻辑就需要简单的看一下 el-table 内部是如何实现的,并直接调用它内部的 API,这里简要阐述一下:

el-table 内部更新列的方法是 updateColumns,我们可以通过 this.store.updateColumns() 来更新列。更新完列之后,还需要调用 doLayout 来重新布局表格,否则可能会出现样式问题。

另外,由于 el-table 内部的列数据是通过 this.store.states._columns 来存储的,通过 v-if 控制的那些动态列发生变化也会被 el-table 内部更新到 _columns 中去,updateColumns 也是参照 _columns 来对表格列进行更新,因此只要修改 _columns 的值然后调用 updateColumns() 就能够让 el-table 自动更新表格数据,基于此我们来实现一个简要的过滤逻辑:

js 复制代码
{
  methods:{
    async filterColumn(column) {
      this.store.states._columns = this.store.states._columns.filter((col) => column?.includes(col.label),);
      this.store.updateColumns();
      await nextTick(); // 等待输完更新完成再 layout,如果同步 layout 可能出现样式问题(如正常的列和 fixed 的列高度出现不一致等情况)
      this.doLayout();
    },
  }
}

4. 总结

以上便是一个用于对 el-table 列进行过滤的简要核心逻辑,当我们在项目中引入该 Mixin 即可进行对表格列的过滤操作了,但仍然有一些细节需要处理,例如我们只在一开始将表格数据写入了 FilterBar 组件中,那些通过数据和 v-if 进行动态控制的列就不会同步到 FilterBar 中去,考虑到这些细节问题并不太适合在文中赘述,我将完整代码放到了 GitHub 上,希望能对各位有一定帮助,有兴趣的同学可以去看看。

效果图片:

相关推荐
别拿曾经看以后~22 分钟前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死25 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人37 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人37 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR43 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍