在 Ant Design Vue 的 a-table 中将特定数据行固定在底部

20 阅读4分钟

前言

在使用 Ant Design Vue 的 a-table 组件时,经常会遇到这样的需求:某些数据行(如汇总行、总计行等)需要始终显示在表格底部,无论其他数据如何排序,这些特殊行都不应该移动位置。

本文将介绍如何通过自定义排序器实现这一功能,并展示从基础实现到优化的完整过程。


问题背景

假设我们有一个展示各模块缺陷统计的表格,其中包含一行"全组"的汇总数据:

模块     | 缺陷数 | 占比   | 缺陷密度
---------|--------|--------|----------
模块A    | 15     | 12%    | 0.5
模块B    | 20     | 15%    | 0.8
模块C    | 10     | 8%     | 0.3
全组     | 45     | 35%    | 1.6

当用户点击表头进行排序时,"全组"这行数据应该始终保持在底部,而其他行则根据实际数值进行排序。


基础实现方案

Ant Design Vue 的 a-table 组件支持通过 sorter 属性自定义排序逻辑。我们可以利用这个特性,在排序器中判断是否为"全组"行,如果是则强制将其排在最后。

示例代码

<script setup>
const columns = [
  { title: "模块", dataIndex: "module", key: "module" },
  {
    title: "缺陷数",
    dataIndex: "bugs",
    key: "bugs",
    sorter: (a, b) => {
      // "全组"始终排在最后
      if (a.module === "全组") return 1;
      if (b.module === "全组") return -1;
      return a.bugs - b.bugs;
    },
  },
  {
    title: "占比",
    dataIndex: "bugRatio",
    key: "bugRatio",
    sorter: (a, b) => {
      // "全组"始终排在最后
      if (a.module === "全组") return 1;
      if (b.module === "全组") return -1;
      const aVal = parseFloat(a.bugRatio) || 0;
      const bVal = parseFloat(b.bugRatio) || 0;
      return aVal - bVal;
    },
  },
];
</script>

排序原理

排序器函数接收两个参数 ab,返回值为:

  • 返回负数:a 排在 b 前面
  • 返回 0:位置不变
  • 返回正数:a 排在 b 后面

通过判断 a.moduleb.module 是否为"全组",我们可以强制改变它们的相对位置。


基础方案的问题

虽然上述方案可以工作,但存在以下问题:

  1. 缺少排序方向支持:没有使用 sortOrder 参数,导致升序和降序时"全组"的返回值逻辑不够清晰
  2. 代码重复:每个可排序列都需要写相同的"全组"判断逻辑,造成大量重复代码
  3. 维护困难:如果需要修改"全组"的判断条件或排序逻辑,需要修改多处代码

优化方案:使用工厂函数

为了解决代码重复问题,我们可以创建一个排序器工厂函数,将公共逻辑抽离出来。

优化后的代码

<script setup>
// 通用排序器工厂函数
const createSorter = compareFn => {
  return (a, b, sortOrder) => {
    if (sortOrder === "ascend") {
      if (a.module === "全组") return 1;
      if (b.module === "全组") return -1;
    } else if (sortOrder === "descend") {
      if (a.module === "全组") return -1;
      if (b.module === "全组") return 1;
    }
    return -compareFn(a, b);
  };
};

const columns = [
  { title: "模块", dataIndex: "module", key: "module" },
  {
    title: "缺陷数",
    dataIndex: "bugs",
    key: "bugs",
    sorter: createSorter((a, b) => a.bugs - b.bugs),
    defaultSortOrder: "ascend",
  },
  {
    title: "占比",
    dataIndex: "bugRatio",
    key: "bugRatio",
    sorter: createSorter((a, b) => {
      const aVal = parseFloat(a.bugRatio) || 0;
      const bVal = parseFloat(b.bugRatio) || 0;
      return aVal - bVal;
    }),
  },
];
</script>

工厂函数说明

createSorter 函数接收一个比较函数 compareFn,返回一个新的排序器函数。内部逻辑:

  1. 升序(ascend):数值小的在前,"全组"在最后
  2. 降序(descend):数值大的在前,"全组"在最后
  3. 默认情况:自动按降序处理

关键点

  • sortOrder 参数由 a-table 组件传入,值为 "ascend""descend"null
  • 在降序时,使用 -compareFn(a, b) 反转比较结果
  • 对于百分比等需要转换类型的字段,在传入的比较函数中处理

完整示例

下面是一个完整的表格组件示例,包含多个可排序列:

<template>
  <a-table :columns="columns" :data-source="dataSource" :pagination="false" bordered />
</template>

<script setup>
import { ref } from "vue";

// 示例数据
const dataSource = ref([
  { module: "模块A", bugs: 15, bugRatio: "12%", avgDensity: 0.5 },
  { module: "模块B", bugs: 20, bugRatio: "15%", avgDensity: 0.8 },
  { module: "模块C", bugs: 10, bugRatio: "8%", avgDensity: 0.3 },
  { module: "全组", bugs: 45, bugRatio: "35%", avgDensity: 1.6 },
]);

// 通用排序器工厂函数
const createSorter = compareFn => {
  return (a, b, sortOrder) => {
    if (sortOrder === "ascend") {
      if (a.module === "全组") return 1;
      if (b.module === "全组") return -1;
    } else if (sortOrder === "descend") {
      if (a.module === "全组") return -1;
      if (b.module === "全组") return 1;
    }
    return -compareFn(a, b);
  };
};

// 表格列定义
const columns = [
  {
    title: "模块",
    dataIndex: "module",
    key: "module",
    width: 150,
    align: "center",
  },
  {
    title: "缺陷数",
    dataIndex: "bugs",
    key: "bugs",
    width: 150,
    align: "center",
    sorter: createSorter((a, b) => a.bugs - b.bugs),
    defaultSortOrder: "ascend",
  },
  {
    title: "占比",
    dataIndex: "bugRatio",
    key: "bugRatio",
    width: 150,
    align: "center",
    sorter: createSorter((a, b) => {
      const aVal = parseFloat(a.bugRatio) || 0;
      const bVal = parseFloat(b.bugRatio) || 0;
      return aVal - bVal;
    }),
  },
  {
    title: "缺陷密度",
    dataIndex: "avgDensity",
    key: "avgDensity",
    width: 150,
    align: "center",
    sorter: createSorter((a, b) => a.avgDensity - b.avgDensity),
  },
];
</script>

扩展:支持多个特殊行

如果需要固定多个特殊行(如"全组"、"总计"等),可以修改工厂函数:

const createSorter = (compareFn, pinnedModules = ["全组", "总计"]) => {
  return (a, b, sortOrder) => {
    const aIsPinned = pinnedModules.includes(a.module);
    const bIsPinned = pinnedModules.includes(b.module);

    if (sortOrder === "ascend") {
      if (aIsPinned && !bIsPinned) return 1;
      if (!aIsPinned && bIsPinned) return -1;
      return compareFn(a, b);
    } else if (sortOrder === "descend") {
      if (aIsPinned && !bIsPinned) return -1;
      if (!aIsPinned && bIsPinned) return 1;
      return -compareFn(a, b);
    }

    if (aIsPinned && !bIsPinned) return -1;
    if (!aIsPinned && bIsPinned) return 1;
    return -compareFn(a, b);
  };
};

// 使用
sorter: createSorter((a, b) => a.bugs - b.bugs, ["全组", "总计"]);

总结

通过自定义排序器,我们可以轻松实现将特定数据行固定在表格底部的需求:

  1. 基础方案:在每个列的 sorter 中判断特殊行
  2. 优化方案:使用工厂函数抽离公共逻辑,减少代码重复
  3. 扩展方案:支持多个特殊行固定

这种方法简单、高效,适用于任何需要固定特定行位置的表格场景。


相关资源