Skip to content

Vue 列排序

点击表头排序图标切换 升序 → 降序 → 取消:列上声明 sortable: true 即显示排序图标;sortMode: 'multiple' 时按住 Shift 点击可叠加多列排序(表头角标显示优先级)。

  • defaultSort 声明初始排序
  • sortMethod 自定义比较器(本地化字符串、业务优先级等),返回值由排序方向自动取反
  • ref 上的 sort() / clearSort() / getSortState() 编程式控制,onSortChange 监听变化

使用的 API

ts
type Column = {
  key: string;  // 列标识(必填)
  title: string;  // 列标题(必填)
  width: number;  // 列宽(必填)
  sortable?: boolean;  // 显示排序图标,点击图标排序
  defaultSort?: 'asc' | 'desc';  // 初始排序
  sortMethod?: (a: Row, b: Row) => number;  // 自定义比较器(升序语义)
}

type Options = {
  list: T[];  // 数据列表
  itemKey: string;  // 行唯一标识字段名
  estimatedSize: number;  // 行预估高度(px)
  sortMode?: 'single' | 'multiple';  // 单列 / Shift 多列,默认 single
  onSortChange?: (state: SortSpec[]) => void;  // 排序变化回调
}

// 实例方法(ref 上调用)
tableRef.value?.sort(colKey, 'asc' | 'desc' | null);  // null 取消该列
tableRef.value?.clearSort();
tableRef.value?.getSortState();  // [{ colKey, order }]

注意

sortMode 为初始化选项,运行时切换需重建实例——示例里用 :key="sortMode" 让 Vue 重建 VirtTableVue。表格存在合并单元格(spanMethod 产生的合并)时排序会被忽略。

完整语义(三态循环、多列优先级、与合并/筛选的关系)见 Vanilla · 列排序

示例

微应用尚未挂载。

源码

点击查看源码
vue
<template>
  <div class="demo-wrapper">
    <h3 class="demo-title">Vue 列排序</h3>
    <div class="demo-hint">
      点击表头的排序图标切换 <b>升序 → 降序 → 取消</b>;多列模式下按住 <b>Shift</b> 点击可叠加排序列(表头角标显示优先级)。
      「姓名」按本地化规则比较,「等级」按 S &gt; A &gt; B &gt; C 业务顺序比较。
    </div>
    <div class="virt-table-controls">
      <label>
        排序模式
        <select v-model="sortMode">
          <option value="multiple">multiple(多列)</option>
          <option value="single">single(单列)</option>
        </select>
      </label>
      <button type="button" class="virt-table-btn" @click="tableRef?.sort('score', 'desc')">按分数降序</button>
      <button type="button" class="virt-table-btn" @click="tableRef?.clearSort()">清除排序</button>
    </div>
    <div class="status-text">{{ status }}</div>
    <div style="width: 660px; height: 480px" class="demo-container">
      <!-- sortMode 是初始化选项,切换时靠 key 重建表格实例 -->
      <VirtTableVue :key="sortMode" ref="tableRef" :columns="columns" :options="options" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { VirtTableVue, type VueTableColumn, type SortSpec } from '@virt-table/vue';
import { faker } from '@faker-js/faker';
import type { VirtTableVueInstance } from '../../virt-table-ref';

interface Row extends Record<string, unknown> {
  id: number;
  name: string;
  age: number;
  score: number;
  level: string;
}

const ROW_COUNT = 2000;
const LEVELS = ['S', 'A', 'B', 'C'];
const LEVEL_RANK: Record<string, number> = { S: 0, A: 1, B: 2, C: 3 };

const columns: VueTableColumn<Row>[] = [
  { key: 'id', title: 'ID', width: 80, sortable: true, defaultSort: 'asc' },
  {
    key: 'name',
    title: '姓名',
    width: 180,
    sortable: true,
    // 自定义比较器:按中文/英文本地化规则比较
    sortMethod: (a, b) => a.name.localeCompare(b.name, 'zh-Hans-CN'),
  },
  { key: 'age', title: '年龄', width: 100, sortable: true },
  { key: 'score', title: '分数', width: 100, sortable: true },
  {
    key: 'level',
    title: '等级',
    width: 100,
    sortable: true,
    // 自定义比较器:按业务顺序 S > A > B > C,而不是字典序
    sortMethod: (a, b) => LEVEL_RANK[a.level]! - LEVEL_RANK[b.level]!,
  },
];

const list: Row[] = Array.from({ length: ROW_COUNT }, (_, i) => ({
  id: i + 1,
  name: faker.person.fullName(),
  age: faker.number.int({ min: 18, max: 65 }),
  score: faker.number.int({ min: 0, max: 100 }),
  level: LEVELS[faker.number.int({ min: 0, max: 3 })]!,
}));

const tableRef = ref<VirtTableVueInstance | null>(null);
const sortMode = ref<'single' | 'multiple'>('multiple');
const sortState = ref<SortSpec[]>([]);

const status = computed(() =>
  sortState.value.length
    ? `当前排序:${sortState.value.map((s, i) => `${i + 1}. ${s.colKey} ${s.order}`).join(' · ')}`
    : '当前排序:无',
);

const options = computed(() => ({
  list,
  itemKey: 'id',
  estimatedSize: 40,
  buffer: 6,
  border: true,
  sortMode: sortMode.value,
  onSortChange: (state: SortSpec[]) => {
    sortState.value = state;
  },
}));
</script>