Bootstrap

Element Plus 跟踪表格数据总数,包括查询、筛选等操作

前言

Element Plus的表格组件提供了筛选功能

前端项目中,如果表格使用的是后端分页,使用表格插件及分页器插件就可以了。这种情况下,前端的表格筛选被后端的分页条件查询取代了

另一种情况:不分页,直接查询所有数据,在前端做查询、筛选、排序

第一种情况下,数据统计由后端直接返回。这里,讨论的是第二种情况

一、需求

想要实现的是在上述第二种情况下,在进行前端筛选、重新获取数据等改变表格内数据总数的操作后,获取该总数。

然而,无法从 ElTable 组件绑定的数据、公开的属性/方法中直接获知

二、解决方案

示例基于 [email protected], [email protected],使用 vue组合式API实现

文末有 vue3 两种代码风格的完整代码

1. 基本示例

下面是个人员表格,可筛选性别、省份

<script setup>
import { ref, computed, onMounted } from "vue";
import { ElButton, ElTable, ElTableColumn } from "element-plus";

const provinces = ["北京", "上海", "广东"];
const tableData = ref([]);
const columns = [
  { label: "No", prop: "no", width: 80 },
  { label: "Name", prop: "name", width: 100 },
  { label: "Age", prop: "age", width: 60 },
  {
    label: "Gender",
    prop: "gender",
    width: 100,
    filterable: true,
    filters: [
      { text: "male", value: "male" },
      { text: "female", value: "female" },
    ],
  },
  {
    label: "Province",
    prop: "province",
    width: 140,
    filterable: true,
    filters: [
      { text: "北京", value: "北京" },
      { text: "上海", value: "上海" },
      { text: "广东", value: "广东" },
    ],
  },
  { label: "Tel", prop: "tel", width: 140 },
];

const tableEl = ref();

onMounted(() => {
  queryHandler();
});

// 获取列表数据
function getTableData(amount = 200) {
  return new Promise((resolve, reject) => {
    resolve(
      Array.from({ length: amount }).map((_, idx) => ({
        no: "No." + (idx + 1),
        name: Math.floor(Math.random() * 900000 + 100000).toString(16),
        age: Math.ceil(Math.random() * 40 + 20),
        gender: Math.random() > 0.5 ? "male" : "female",
        province: provinces[Math.floor(Math.random() * 3)],
        tel: "13311112222",
      }))
    );
  });
}
async function queryHandler() {
  tableEl.value?.clearFilter();
  const amount = Math.floor(Math.random() * 900) + 100;
  tableData.value = await getTableData(amount);
}

function filterHandler(value, row, column) {
  const property = column["property"];
  return row[property] === value;
}
</script>

<template>
  <el-table ref="tableEl" :data="tableData" max-height="500">
    <template v-for="col in columns" :key="col.prop">
      <el-table-column
        v-if="col.filterable"
        :prop="col.prop"
        :label="col.label"
        :width="col.width"
        :filters="col.filters"
        :filter-method="filterHandler"
      ></el-table-column>
      <el-table-column
        v-else
        :prop="col.prop"
        :label="col.label"
        :width="col.width"
      ></el-table-column>
    </template>
  </el-table>
  <el-button @click="queryHandler">重新请求列表数据</el-button>
</template>

从表格绑定数据 tableData 中无法获知筛选后的表格数据总量,查阅 Element Plus 官方文档,该组件也没有提供相应接口,那就只能想办法通过某种方案间接实现了

2. 方案

  • 方案一:直接将筛选方法作用于 tableData,手动计算筛选后的表格数据总数

这种方案需要监听表格的 filter-change 事件,然后过滤

由于 filter-change 事件返回的当前更改的筛选条件,存在多个筛选时,需存储过滤条件

总结一下:

> 给表项绑定 column-key 属性;创建存储筛选条件的对象
> 监听表格的 filter-change 事件,更新筛选条件,计算筛选后的表格数据总数
> 表格数据变更时,清空筛选条件

上述第二步,手动计算筛选如果过于复杂,也可以考虑在DOM层面上,在表格渲染完毕后,直接查询条数(<tr>)。

  • 方案二:监听表格组件内部数据

虽然官方未提供直接的属性与方法,但表格组件内部肯定知道满足筛选条件的数据有哪些,问题在于它是否被暴露出来

通过打印表格对象,发现store中找到了相关数据,ElTable 组件内部的状态存储信息:

在这里插入图片描述
更完美的是,它是响应式变量,意味着可以直接监听

上例中,我们只需要中添加一个计算变量即可:

import { ref, computed, onMounted } from "vue";

const tableEl = ref();
const total = computed(
  () => tableEl.value?.store?.states?.data?.value?.length ?? 0
);

注意:若使用的是vue选项式API实现,需等待表格组件挂载后,再添加计算变量

三、总结

第一种方案监听筛选事件,再在监听处理事件中手动筛选数据、计算总数,较为繁琐,不太建议使用

方案二依赖于 ElTable 组件内部的状态存储信息,非官网提供的属性/方法,随着版本的更替,指不定什么时候就成潜在的 “Breaking Change” 了。建议核实后锁定项目的 element-plus 的版本

若官方后续提供相应属性/方法,再替换。

四、完整代码

Demo - 基于vue选项式API

Demo - 基于vue组合式API:

<script setup>
import { ref, computed, onMounted } from "vue";
import { ElButton, ElTable, ElTableColumn } from "element-plus";

const provinces = ["北京", "上海", "广东"];
const tableData = ref([]);
const columns = [
  { label: "No", prop: "no", width: 80 },
  { label: "Name", prop: "name", width: 100 },
  { label: "Age", prop: "age", width: 60 },
  {
    label: "Gender",
    prop: "gender",
    width: 100,
    filterable: true,
    filters: [
      { text: "male", value: "male" },
      { text: "female", value: "female" },
    ],
  },
  {
    label: "Province",
    prop: "province",
    width: 140,
    filterable: true,
    filters: [
      { text: "北京", value: "北京" },
      { text: "上海", value: "上海" },
      { text: "广东", value: "广东" },
    ],
  },
  { label: "Tel", prop: "tel", width: 140 },
];

const tableEl = ref();
const total = computed(
  () => tableEl.value?.store?.states?.data?.value?.length ?? 0
);

onMounted(() => {
  queryHandler();
});

// 获取列表数据
function getTableData(amount = 200) {
  return new Promise((resolve, reject) => {
    resolve(
      Array.from({ length: amount }).map((_, idx) => ({
        no: "No." + (idx + 1),
        name: Math.floor(Math.random() * 900000 + 100000).toString(16),
        age: Math.ceil(Math.random() * 40 + 20),
        gender: Math.random() > 0.5 ? "male" : "female",
        province: provinces[Math.floor(Math.random() * 3)],
        tel: "13311112222",
      }))
    );
  });
}
async function queryHandler() {
  tableEl.value?.clearFilter();
  const amount = Math.floor(Math.random() * 900) + 100;
  tableData.value = await getTableData(amount);
}

function filterHandler(value, row, column) {
  const property = column["property"];
  return row[property] === value;
}
</script>

<template>
  <h3>TableData length: {{ tableData.length }}</h3>
  <h3>Total entries: {{ total }}</h3>
  <el-table ref="tableEl" :data="tableData" max-height="500">
    <template v-for="col in columns" :key="col.prop">
      <el-table-column
        v-if="col.filterable"
        :prop="col.prop"
        :label="col.label"
        :width="col.width"
        :filters="col.filters"
        :filter-method="filterHandler"
      ></el-table-column>
      <el-table-column
        v-else
        :prop="col.prop"
        :label="col.label"
        :width="col.width"
      ></el-table-column>
    </template>
  </el-table>
  <el-button @click="queryHandler">重新请求列表数据</el-button>
</template>
;