前言

本篇文章记述了如何在Vue3+Element Plus 技术栈下 实现一个具有筛选、排序、分页功能的表格,并将其封装成一个组件的过程。

1.完成基础表格

我们先使用el-table绘制一个基础的表格:

<template>
  <div class="cl-PaginationTable">
    <el-table :data="tableData" height="320" >
      <el-table-column
        v-for="col of tableColumns"
        :key="col.data"
        :prop="col.data"
        :label="col.label" />
    </el-table>
  </div>
</template>

<script setup>
import { ElTable, ElTableColumn } from "element-plus";
import { getPeopleInfo } from "@/api/people.js";
import { onMounted, ref } from "vue";

// t- 初始化表格数据
const tableData = ref([]);
const tableColumns = [
  {
    label: "人员",
    data: "name",
  },
  {
    label: "地址",
    data: "address",
  },
  {
    label: "签到时间",
    data: "time",
  },
];
async function getTableData() {
  const res = await getPeopleInfo();
  tableData.value = res.data;
}
onMounted(async () => {
  getTableData();
});
</script>

<style lang="less" scoped>
.cl-PaginationTable {
  width: 500px;
  height: 300px;
  border: 3px solid;
  margin: 0 auto;
  padding-bottom: 40px;
}
</style>

这一部分比较简单,el-table组件通过data属性设置表格数据,通过el-table-column组件设置表格列。表格数据tableData通过接口请求到,表格列数据tableColumns,由于只有三列比较简单,由我自己编写。

效果如下:

2.实现排序功能

(1) 阅读文档

从文档中的这段介绍可以提炼出如下的几点信息:

  1. el-table-columnsortable属性可以实现以该列为基准的排序
  2. el-tabledefault-sort可以设置默认的排序方式
  3. el-table-columnsort-methodsort-by属性可以自定义排序规则
  4. 如果sortable属性被设置为custom,就可以在排序时,触发sort-change事件,在事件回调中可以获取当前排序字段名和排序顺序

文档中与表格排序相关的属性、方法如下:

Table属性

default-sort

指定默认排列顺序,接收一个对象其中要有prop和order两个属性,prop指定排序基准列,order指定排序方式('ascending'or 'descending')

Table事件

sort-change

表格排序条件变化事件,需要 sortable属性设置为custom

Table方法

sort

手动排序,接收两个参数prop和order

clearSort

清除排序条件,数据恢复成未排序的状态

Table-column属性

sortable

列是否可以排序

sort-method

自定义排序方法,接收一个函数,这个函数参考Array.prototype.sort()的排序函数。

sort-by

指定数据按照哪个属性排序

sort-orders

数据在排序时所使用排序策略的轮转顺序。 需传入一个数组,随着用户点击表头,该列依次按照数组中元素的顺序进行排序 (数组内容:['ascending', 'descending', null])

(2) 排序方案总结

梳理一下,element表格的排序方法有如下的几种:

  1. 默认排序:通过default-sort实现加载时自动排序
  2. 点击表头排序-普通版:通过sortable实现,用户点击表头手动排序,但是只能是基本的升序和降序
  3. 点击表头排序-强化版:通过sortable+ sort-method/sort-by,实现点击表头排序,但是是按自定义的方式
  4. 方法排序:通过sort方法进行手动排序,这种方案就可以将排序与表格之外的其它功能相关联了,有很大的自由度
  5. 数据驱动的排序:侦听sort-change事件,当需要排序时,更改表格数据,从而实现排序

这里我最终选择了第五种方案,原因是因为需要考虑到后面的分页功能,由于我的表格需要添加分页功能,因此表格只会显示一页的数据,这时前四种方法就只能针对这一页的数据进行排序,显然无法达到我们的需求。

(3) 功能实现

选定方案之后就可以进行功能实现,大致分为三步:

  1. 通过sortable属性锚定需要排序的列
  2. 侦听sort-change事件
  3. 自定义一个排序函数用于处理sort-change事件

我目前的表格只需要依据"签到时间"这一列进行排序,因此我给tableColumns中签到时间列对象添加了一个sortable属性

const tableColumns = [
  {
    label: "人员",
    data: "name",
  },
  {
    label: "地址",
    data: "address",
  },
  {
    label: "签到时间",
    data: "time",
    sortable: "custom",
  },
];

之后侦听el-tablesort-change事件,并且我写了一个排序方法,基于时间对tableData进行排序

// 排序事件处理函数
function tableSortChange({ column, prop, order }) {
  //时间排序方法
  function timeCompare(time1, time2) {
    time1 = new Date(time1.replace("-", "/"));
    time2 = new Date(time2.replace("-", "/"));

    return time1 > time2;
  }
  if (order == "ascending") {
    // 升序
    tableData.value.sort((a, b) => (timeCompare(a[prop], b[prop]) ? 0 : -1));
  } else {
    //降序
    tableData.value.sort((a, b) => (timeCompare(a[prop], b[prop]) ? -1 : 0));
  }

}

3.实现筛选功能

(1)阅读文档

查看一下文档中的示例我们可以看到,官方推荐的筛选方法是给列添加filtersfilter-method两个属性,前者设置筛选选项,后者设置筛选规则。


按照这种方法我就可以这样写:

初始化时调用addFilterOptions函数添加筛选相关的列配置,之后将这些配置作为filtersfilter-method的属性值

// t-筛选
// 添加筛选选项
function addFilterOptions() {
  //需要添加筛选功能的列
  const filterCols = ["name", "address"];
  tableColumns.value.forEach(el => {
    if (filterCols.includes(el.data)) {
      el.filterOptions = lodash
        .uniq(tableData.value.map(item => item[el.data]))
        .map(value => ({ text: value, value }));

      el.filterMethod = filterMethod;
    }
  });
}
function filterMethod(value, row, column) {
  const property = column["property"];
  return row[property] === value;
}

这样可以实现筛选功能,但是与前面的排序功能类似,由于考虑到之后表格要实现分页功能,那时tableData就仅为一页的数据,就无法实现我们期望的筛选效果。因此也要仿照排序功能,放弃组件内置的筛选功能,转而使用“数据驱动”的实现方案。

借鉴一下排序的思路,这里我们可以使用filter-change事件来实现筛选:

(2) 功能实现

筛选功能我们最终也选择了数据驱动的方案,主要步骤与排序类似:

  1. 通过filters属性锚定需要筛选的列并设置筛选选项
  2. 设置filter-multiple属性允许筛选时多选
  3. 侦听筛选事件filter-change
  4. 自定义一个数据筛选方法,用于处理filter-change事件

首先我们将之前使用的筛选方法(filter-method属性)去掉,转而侦听filter-change事件,并通过修改tableData的方式实现筛选。


但是这里就有一个问题了,我们现在的筛选是将请求到的tableData数据中的一部分过滤出来,设置为新的tableData,如果之后要重新筛选呢?要还原呢?原来的数据岂不是已经没有了。

这里其实有两个方案,一是将筛选的工作交给后端处理,我们只需要将筛选条件传给后端,重新请求数据即可。二是我们设置两个tableData,一个用来存储请求到的总数据,一个用来存储用于表格展示的数据。

这里我选择使用方案一,这种方案的好处是对后端的依赖较小,可以适配多种数据获取方式(实际我这里使用的就不是真正的后端接口,而是随机生成的mock数据)。

因此我针对性的设计了一套数据池体系:

  1. 总数据池,存储在数据库中的所有的数据。
  2. 请求数据池,它由我们通过接口从数据库中获取的数据构成,我们在请求原始数据时会将一些请求条件作为参数传递给后端,后端会根据这些条件从数据库(总数据池)中获取相应的数据返回给我们,这些返回的数据就构成了请求数据池。
  3. 过滤数据池,当表格进行筛选、排序等操作时,对请求数据池进行过滤后所获得的数据。
  4. 展示数据池,根据当前的页码、页容量从过滤数据池中截取出来的一页数据。

其中请求数据池、过滤数据池和展示数据池是在前端,因此我就将tableData分为这三部分:

const tableData = ref({
  queryDataPool: [], //请求数据池
  filterDataPool: [], //过滤数据池
  displayDataPool: [], //展示数据池
});

最后过滤的代码如下:

// t-筛选
// 添加筛选选项
function addFilterOptions() {
  //需要添加筛选功能的列
  const filterCols = ["name", "address"];
  tableColumns.value.forEach(el => {
    if (filterCols.includes(el.data)) {
      el.filterOptions = lodash
        .uniq(tableData.value.queryDataPool.map(item => item[el.data]))
        .map(value => ({ text: value, value }));
    }
  });
}
// 记录筛选条件
let filterConditions = ref({});
// 筛选方法
function filterMethod(filters) {
  Object.assign(filterConditions.value, filters);

  tableData.value.displayDataPool = tableData.value.queryDataPool.filter(el =>
    Object.entries(filterConditions.value).every(
      item => item[1].includes(el[item[0]]) || item[1].length == 0
    )
  );
}

(3) 功能优化

优化一:部分数据的地址属性为空,因此筛选时也有这样一个空白的选项,我们需要将空白的选项改名为无地址,并且要让其排列在整个筛选列表的最后面。

function addFilterOptions() {
  tableColumns.value.forEach(el => {
    el.filterOptions = lodash
      .uniq(tableData.value.queryDataPool.map(item => item[el.key]))
      .map(value => ({ text: value, value }));

    if (el.filterOptions.findIndex(item => item.text == "") > -1) {
      el.filterOptions = el.filterOptions.filter(item => item.text !== "");
      el.filterOptions.push({
        text: "空",
        value: "",
        isUse: false,
      });
    }
  });
}

4.实现分页功能

(1)分页作用及基本概念

分页主要是用于处理“大批量数据的展示”的情况,它主要解决了两个方面的问题:

  1. 用户查看数据不便的问题。假设现在我需要再表格中展示几百条数据,此时这个表格实际就是一个长列表。此时用户想要查看后面的数据就必需不断地拖动滑条或者滚动鼠标滚轮,实际上就非常不方便。添加分页功能后,只需检索页码即可。
  2. 数据展示时的性能问题。如果数据量过大,可能就会出现请求和处理数据的时间过长或者渲染时页面卡顿的问题。如果添加了分页功能,那么一次只需请求和渲染一页数据,就不会存在性能问题。

想要实现分页就必须要先了解与分页相关的几个基本概念:

  1. 总条目数,指需要展示的数据总量(总条数)。
  2. 页容量,每一页的数据量。
  3. 总页数,总共有多少页,用总条目数除以页容量就可以计算得到。
  4. 当前页码,当前显示第几页的数据, 当前页所显示数据的索引为(当前页码 - 1)* 页容量 ~ 当前页码 * 页容量

(2)功能实现

Element-ui的表格组件本身不具备分页功能,因此想要实现分页就要使用分页组件el-pagination

el-pagination组件使用起来并不复杂,主要就是针对性的去设置前面提到的分页的基本概念.

  1. total属性设置总条目数;
  2. page-size设置页容量;
  3. v-model:current-page双向绑定当前页码
<el-pagination
  small
  background
  layout="prev, pager, next"
  v-model:current-page="pageNum"
  :page-size="pageSize"
  :total="itemTotal" />

最终实现的代码如下:

// t-分页
const itemTotal = computed(() => tableData.value.filterDataPool.length); //总条目数
const pageSize = ref(7); //页容量
const pageNum = ref(1); //当前页码
// 根据页码设置当前展示数据
function setDisplayDataPool(n, s) {
  s = s || pageSize.value;
  pageNum.value = n;

  tableData.value.displayDataPool = tableData.value.filterDataPool.slice(
    (n - 1) * s,
    n * s
  );
}

// 当页码改变时重设展示据池数据
watch(pageNum, value => setDisplayDataPool(value));
// 当过滤数据池改变时重设展示数据池数据
watch(
  () => tableData.value.filterDataPool,
  () => setDisplayDataPool(1),
  {
    deep: true,
  }
);

5.表格优化

(1)国际化

Element Plus 的组件默认的语言是英文。例如筛选功能中的按钮。

那么我们就需要将语言切换为中文,方式如下:

import { createApp } from 'vue'
import App from './App.vue'
import "@/mock/index"

import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'


const app = createApp(App)

app.use(ElementPlus, {
  locale: zhCn,
})

app.mount('#app')

(2)处理数据为空的情况

我现在的表格数据是通过接口请求到的,那么就要考虑接口不能正常获取数据的情况。主要是有如下的两种场景:

Ⅰ. 请求失败接口报错

针对这种情况,我们要进行一下错误的捕获与处理。

async function getTableData() {
  try {
    const res = await getPeopleInfo();
    // 模拟报错的情况
    throw new Error('无法获取表格数据')

    if (res.code == 200) {
      tableData.value.queryDataPool = res.data;
      tableData.value.filterDataPool = res.data;
    }else{
      console.log('表格数据异常:',res);
    }
  } catch (error) {
    console.log('表格数据异常',error);
  }
}

Ⅱ. 返回的数据为空

当数据为空时,组件则已经帮我们做好了对应的效果。如果对这个效果不满意也可以通过表格组件的 empty插槽进行修改。

async function getTableData() {
  try {
    let res = await getPeopleInfo();
    // 模拟数据为空的场景
    res.data = []
    // throw new Error('无法获取表格数据')

    if (res.code == 200) {
      tableData.value.queryDataPool = res.data;
      tableData.value.filterDataPool = res.data;
    }else{
      console.log('表格数据异常:',res);
    }
  } catch (error) {
    console.log('表格数据异常:',error);
  }
}

6.组件封装

我的最终目的还是将这个带有分页功能的表格封装成一个组件。封装组件的重点还是在组件接口设计(属性、方法、事件、插槽),它是沟通组件内外的桥梁,下面我将以功能为单位介绍我的封装过程。

(1) 封装基础表格

基础表格部分设计了两个属性:tableData 表格数据 和tableColumns表格列配置。

//t-定义props
const props = defineProps({
  tableData: {
    type: Array,
    required: true,
  },
  tableColumns: {
    type: Array,
    required: true,
  },
});

tableData示例

[
  // 第一行数据
  {
    name:'孙悟空',//第一行第一列
    address:'花果山',//第一行第二列
    time:'2024-02-02 09:48:12'//第一行第三列
  }
  // 第二行数据
  ......
]

tableColumns示例

[
  // 第一列配置
  {
    lable:'人员',//列名
    key:'name',//该列数据在tableData中的key
  }
  // 第二列配置
  ......
]

(2) 封装排序功能

排序功能是一个难点,主要因为在我们当前的表格中的排序功能是自定义的。但是如果每次使用组件,都把让用户自己定义排序规则,那就太麻烦了。这里可以保留自定义排序规则的接口,但是必需有一套默认的排序规则。这套默认的排序规则必需要考虑到列数据各种可能得情况。简单的梳理一下这部分的逻辑:

最后排序部分我是这样写的:

// 排序事件处理函数
function tableSortChange({ column, prop, order }) {
  // 取消排序
  if (order == null) {
    clearSort();
    return;
  }

  // 获取自定义方法
  let sortMethod = tableColumns.value.find(
    item => item.key == prop
  )?.sortMethod;

  if (sortMethod) {
    //使用自定义排序方法
    tableDataPool.value.filterDataPool.sort((a, b) => {
      return order == "ascending"
        ? sortMethod(a[prop], b[prop])
        : sortMethod(b[prop], a[prop]);
    });
  } else {
    //使用默认排序方法
    tableDataPool.value.filterDataPool.sort((a, b) =>
      defaultSort(a[prop], b[prop], order == "ascending")
    );
  }
}

// 默认排序方法
function defaultSort(value1, value2, bol) {
  // 字符串比较
  function stringCompare(value1, value2, bol) {
    return bol ? value1.localeCompare(value2) : value2.localeCompare(value1);
  }
  // 数字比较
  function numberCompare(value1, value2, bol) {
    return bol ? value1 - value2 : value2 - value1;
  }

  return typeof value1 === "string"
    ? stringCompare(...arguments)
    : numberCompare(...arguments);
}

// 清除排序
function clearSort() {
  // 重新从请求数据池中筛选数据
  filterData();
}

在排序功能部分,给tableColumns属性中的对象添加了两个新的属性sortablesortMethod

[
  // 第一列配置
  {
    lable:'人员',//列名
    key:'name',//该列数据在tableData中的key
    sortable:true,//该列启动排序功能
    sortMethod:(a,b)=>{...},//自定义排序方法,参考Array.prototype.sort方法的比较函数
  }
  // 第二列配置
  ......
]

(3) 封装筛选功能

Ⅰ.封装代码

筛选功能封装较为简单,主要代码如下:

// 筛选条件
let filterConditions = ref({});

// 初始化
function initFilter() {
  // 初始化筛选条件
  filterConditions.value = tableColumns.value.reduce((accumulator, el) => {
    if (el.filter) {
      accumulator[el.key] = [];
    }
    return accumulator;
  }, {});
  //初始化筛选功能
  tableColumns.value.forEach(el => {
    if (Object.keys(filterConditions.value).includes(el.key)) {
      el.filterOptions = lodash
        .uniq(tableDataPool.value.queryDataPool.map(item => item[el.key]))
        .map(value => ({ text: value, value }));

      if (el.filterOptions.findIndex(item => item.text == "") > -1) {
        el.filterOptions = el.filterOptions.filter(item => item.text !== "");
        el.filterOptions.push({
          text: "空",
          value: "",
          isUse: false,
        });
      }
    }
  });
}

// 筛选事件处理函数,更新筛选条件
function filterMethod(filters) {
  // 更新filterConditions
  Object.assign(filterConditions.value, filters);
  filterData();
}

//筛选数据
function filterData() {
  tableDataPool.value.filterDataPool = tableDataPool.value.queryDataPool.filter(
    el =>
      Object.keys(filterConditions.value).every(
        key =>
          filterConditions.value[key].includes(el[key]) ||
          filterConditions.value[key].length == 0
      )
  );
}

Ⅱ.组件接口

在筛选功能部分,给tableColumns属性中的对象添加了新的属性filter,用于指示该列是否开启筛选功能

[
  // 第一列配置
  {
    lable:'人员',//列名
    key:'name',//该列数据在tableData中的key
    sortable:true,//该列启动排序功能
    sortMethod:(a,b)=>{...},//自定义排序方法,参考Array.prototype.sort方法的比较函数
    filter:true, // filter为true时代表该列启动筛选功能
  }
  // 第二列配置
  ......
]

(4) 封装页码功能

Ⅰ.封装代码

分页功能功能也没有特别的难点,主要代码如下:

const itemTotal = computed(() => tableDataPool.value.filterDataPool.length); //总条目数
const pageNum = ref(1); //当前页码
// 根据页码设置当前展示数据
function setDisplayDataPool(n, s) {
  s = s || props.pageSize
  pageNum.value = n;

  tableDataPool.value.displayDataPool =
    tableDataPool.value.filterDataPool.slice((n - 1) * s, n * s);
}

// 当页码改变时重设展示据池数据
watch(pageNum, value => setDisplayDataPool(value));
// 当过滤数据池改变时重设展示数据池数据
watch(
  () => tableDataPool.value.filterDataPool,
  () => setDisplayDataPool(1),
  {
    deep: true,
  }
);

Ⅱ.组件接口

在分页部分给组件新增了一个属性pageSize,类型为Number,用于表示页容量。

(5) 组件接口汇总

Ⅰ.组件属性

const props = defineProps({
  tableData: {
    type: Array,
    required: true,
  },
  tableColumns: {
    type: Array,
    required: true,
  },
  pageSize: {
    type: Number,
    required: true,
  },
});

属性名

介绍

示例

tableData

表格数据

tableColumns

表格列配置

pageSize

表格页容量

除了之前的设计的组件属性外,我还设计了一批组件方法,以便在某些情况下控制组件功能

Ⅱ.组件方法

defineExpose({
  clearSort,
  clearFilter,
  setPage,
});

方法名

介绍

参数

clearSort

清除排序,将表格数据的顺序恢复到排序前的样子

-

clearFilter

清除筛选效果

-

setPage

将表格翻到某一页

num : 需要跳转到的页码

Ⅲ.组件事件

const $emit = defineEmits([
  "sort-change",
  "filter-change",
  "page-change",
  "row-click",
]);

事件名

介绍

事件参数

row-click

当某一行被点击时会触发该事件

row :行所对应的数据

filter-change

当表格的过滤条件发生变化时会触发该事件

filters:新的过滤条件

示例:{name:['张三','李四']}

sort-change

当表格的排序条件发生变化时会触发该事件

prop:排序的基准列

order:排序方式(ascending升序;descending降序;null不排序)

page-change

当表格的页码变化时会触发该事件

pageNum : 当前页码

pageSize: 页容量

(6) 完整的组件代码

<template>
  <div class="cl-PaginationTable">
    <el-table
      ref="ElTableDom"
      highlight-current-row
      :data="tableDataPool.displayDataPool"
      @sort-change="tableSortChange"
      @filter-change="filterMethod"
      @row-click="rowClick">
      <el-table-column
        v-for="col of tableColumns"
        :key="col.key"
        :column-key="col.key"
        :prop="col.key"
        :label="col.label"
        :sortable="col.sortable"
        :sort-method="col.sortMethod"
        :filters="col.filterOptions"
        filter-multiple />
    </el-table>
    <el-pagination
      small
      background
      layout="prev, pager, next"
      v-model:current-page="pageNum"
      :page-size="props.pageSize"
      :total="itemTotal" />
  </div>
</template>

<script setup>
  import lodash from "lodash";
  import { ElTable, ElTableColumn, ElPagination } from "element-plus";
  import { getPeopleInfo } from "@/api/people.js";
  import { computed, onMounted, ref, watch } from "vue";

  //t-自定义属性
  const props = defineProps({
    tableData: {
      type: Array,
      required: true,
    },
    tableColumns: {
      type: Array,
      required: true,
    },
    pageSize: {
      type: Number,
      required: true,
    },
  });

  // t-自定义事件
  const $emit = defineEmits([
    "sort-change",
    "filter-change",
    "page-change",
    "row-click",
  ]);

  const ElTableDom = ref(null);

  // t-初始化
  const isLoading = ref(true);
  onMounted(async () => {
    initDataPool();
    initTableColumns();
  });

  // t- 表格数据池
  const tableDataPool = ref({
    queryDataPool: [], //请求数据池
    filterDataPool: [], //过滤数据池
    displayDataPool: [], //展示数据池
  });

  // t-列数组
  const tableColumns = ref([]);

  // t-初始化数据池
  function initDataPool() {
    tableDataPool.value.queryDataPool = lodash.cloneDeep(props.tableData);
    tableDataPool.value.filterDataPool = lodash.cloneDeep(props.tableData);
  }

  // t-初始化列数组
  function initTableColumns() {
    tableColumns.value = props.tableColumns;
    addSortOptions();
    initFilter();
  }

  // t-排序
  // 添加排序选项
  function addSortOptions() {
    tableColumns.value.forEach(el => {
      el.sortable = el.sortable && "custom";
    });
  }

  // 存储排序信息
  const sortInfo = ref({
    order: null,
    prop: "",
  });

  // 排序事件处理函数
  function tableSortChange({ column, prop, order }) {
    sortInfo.value.order = order;
    sortInfo.value.prop = prop;

    $emit("sort-change", prop, order);
    // 取消排序
    if (order == null) {
      // 重新从请求数据池中筛选数据
      filterData();
      return;
    }

    // 获取自定义方法
    let sortMethod = tableColumns.value.find(
      item => item.key == prop
    )?.sortMethod;

    if (sortMethod) {
      tableDataPool.value.filterDataPool.sort((a, b) => {
        return order == "ascending"
          ? sortMethod(a[prop], b[prop])
          : sortMethod(b[prop], a[prop]);
      });
    } else {
      tableDataPool.value.filterDataPool.sort((a, b) =>
        defaultSort(a[prop], b[prop], order == "ascending")
                                             );
    }
  }

  // 默认排序方法
  function defaultSort(value1, value2, bol) {
    // 字符串比较
    function stringCompare(value1, value2, bol) {
      return bol ? value1.localeCompare(value2) : value2.localeCompare(value1);
        }
  // 数字比较
  function numberCompare(value1, value2, bol) {
    return bol ? value1 - value2 : value2 - value1;
  }

  return typeof value1 === "string"
    ? stringCompare(...arguments)
    : numberCompare(...arguments);
}

// public 清除排序
function clearSort() {
  // 重新从请求数据池中筛选数据
  // filterData();
  tableSortChange({
    prop: "",
    order: null,
  });
}

// t-筛选
// 筛选条件
let filterConditions = ref({});

// 初始化
function initFilter() {
  // 初始化筛选条件
  filterConditions.value = tableColumns.value.reduce((accumulator, el) => {
    if (el.filter) {
      accumulator[el.key] = [];
    }
    return accumulator;
  }, {});
  //初始化筛选功能
  tableColumns.value.forEach(el => {
    if (Object.keys(filterConditions.value).includes(el.key)) {
      el.filterOptions = lodash
        .uniq(tableDataPool.value.queryDataPool.map(item => item[el.key]))
        .map(value => ({ text: value, value }));

      if (el.filterOptions.findIndex(item => item.text == "") > -1) {
        el.filterOptions = el.filterOptions.filter(item => item.text !== "");
        el.filterOptions.push({
          text: "空",
          value: "",
          isUse: false,
        });
      }
    }
  });
}

// 筛选事件处理函数,更新筛选条件
function filterMethod(filters) {
  $emit("filter-change", filters);
  // 更新filterConditions
  Object.assign(filterConditions.value, filters);
  filterData();
}

//筛选数据
function filterData() {
  tableDataPool.value.filterDataPool = tableDataPool.value.queryDataPool.filter(
    el =>
      Object.keys(filterConditions.value).every(
        key =>
          filterConditions.value[key].includes(el[key]) ||
          filterConditions.value[key].length == 0
      )
  );
}

// public 清除筛选
function clearFilter() {
  // 清除筛选效果
  ElTableDom.value.clearFilter();
  // 数据还原
  tableDataPool.value.filterDataPool = tableDataPool.value.queryDataPool;
  // 重新排序
  tableSortChange(sortInfo.value);
}

// t-分页
const itemTotal = computed(() => tableDataPool.value.filterDataPool.length); //总条目数
const pageNum = ref(1); //当前页码
// 根据页码设置当前展示数据
function setDisplayDataPool(n, s) {
  s = s || props.pageSize;

  const maxPageNum = Math.ceil(itemTotal.value / props.pageSize);
  if (n > maxPageNum) {
    n = maxPageNum;
  }
  pageNum.value = n;

  $emit("page-change", n, s);

  tableDataPool.value.displayDataPool =
    tableDataPool.value.filterDataPool.slice((n - 1) * s, n * s);
}

// 当页码改变时重设展示据池数据
watch(pageNum, value => setDisplayDataPool(value));
// 当过滤数据池改变时重设展示数据池数据
watch(
  () => tableDataPool.value.filterDataPool,
  () => setDisplayDataPool(1),
  {
    deep: true,
  }
);

// public 设置页
function setPage(num) {
  setDisplayDataPool(num);
}

function rowClick(row, column, event) {
  $emit("row-click", row);
}

defineExpose({
  clearSort,
  clearFilter,
  setPage,
});
</script>

<style lang="less" scoped>
.cl-PaginationTable {
  width: 100%;
  height: 100%;
  margin: 5px 0;
}
</style>

参考资料

  1. Table 表格 | Element Plus
  2. Pagination 分页 | Element Plus
  3. 国际化 | Element Plus
  4. JS数组对象——中文按照首字母排序(分组)sort()、localeCompare()_js汉字按字母顺序排序-CSDN博客
  5. String.prototype.localeCompare() - JavaScript | MDN

 

Logo

前往低代码交流专区

更多推荐