Skip to content

列排序

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

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

相关能力已拆分为独立示例:自动合计行加载态导出 CSV/Excel列显隐/列设置键盘导航列筛选

使用的 API

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

type Options = {
  sortMode?: 'single' | 'multiple';      // 单列 / Shift 多列,默认 single
  onSortChange?: (state: SortSpec[]) => void;
};

// 实例方法
table.sort(colKey, 'asc' | 'desc' | null); // null 取消该列
table.clearSort();
table.getSortState(); // [{ colKey, order }]

注意

sortMode 为初始化选项,运行时切换需重建实例;表格存在合并单元格(spanMethod 产生的合并)时排序会被忽略。

示例

微应用尚未挂载。

源码

点击查看源码
ts
import { faker } from '@faker-js/faker';
import {
  VirtTable,
  type SortSpec,
  type VirtTableColumn,
} from '@virt-table/vanilla';

interface Row {
  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 };

export function bootstrapTableSort(root: HTMLElement): () => void {
  const columns: VirtTableColumn<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 })],
  }));

  root.innerHTML = `
    <div class="demo-hint">
      点击表头的排序图标切换 <b>升序 → 降序 → 取消</b>;多列模式下按住 <b>Shift</b> 点击可叠加排序列(表头角标显示优先级)。
      「姓名」按本地化规则比较,「等级」按 S &gt; A &gt; B &gt; C 业务顺序比较。
    </div>
    <div class="virt-table-controls">
      <label>
        排序模式
        <select id="mode">
          <option value="multiple">multiple(多列)</option>
          <option value="single">single(单列)</option>
        </select>
      </label>
      <button class="virt-table-btn" id="btnScore">按分数降序</button>
      <button class="virt-table-btn" id="btnClear">清除排序</button>
      <span id="state" class="demo-note"></span>
    </div>
    <div style="width:660px;height:480px;" class="demo-container" id="c"></div>`;

  const container = root.querySelector('#c') as HTMLElement;
  const stateEl = root.querySelector('#state') as HTMLElement;
  const modeEl = root.querySelector('#mode') as HTMLSelectElement;

  const renderState = (state: SortSpec[]) => {
    stateEl.textContent = state.length
      ? `当前排序:${state.map((s, i) => `${i + 1}. ${s.colKey} ${s.order}`).join(' · ')}`
      : '当前排序:无';
  };

  // sortMode 为初始化选项,切换时重建实例
  let table: VirtTable<Row>;
  const create = (sortMode: 'single' | 'multiple') => {
    table = new VirtTable<Row>(container, {
      list,
      columns,
      itemKey: 'id',
      estimatedSize: 40,
      buffer: 6,
      border: true,
      sortMode,
      onSortChange: renderState,
    });
    renderState(table.getSortState());
  };
  create('multiple');

  modeEl.onchange = () => {
    table.destroy();
    create(modeEl.value as 'single' | 'multiple');
  };
  (root.querySelector('#btnScore') as HTMLButtonElement).onclick = () =>
    table.sort('score', 'desc');
  (root.querySelector('#btnClear') as HTMLButtonElement).onclick = () =>
    table.clearSort();

  return () => {
    table.destroy();
    root.innerHTML = '';
  };
}