likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 2

1. 项目背景

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

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

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

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

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 目录用于存放该功能的代码,整理目录结构如下:

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

其中:

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

3.1 创建 FilterBar

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

<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 事件时执行对表格列的过滤操作:

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 自动更新表格数据,基于此我们来实现一个简要的过滤逻辑:

{
  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 上,希望能对各位有一定帮助,有兴趣的同学可以去看看。

效果图片:

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