Skip to content

React 高级筛选(筛选管理器)

列筛选有两种入口,可同时使用:表头快捷筛选(见 列筛选)与筛选管理器<VirtFilterBuilderReact /> —— 渲染在表格之外,用 AND/OR 条件组表达 「分数 ≥ 80 (部门属于工程部/设计部 级别为 P8)」这类复合条件,支持任意层嵌套。

两者是独立状态,最终以 AND 组合。

使用的 API

tsx
import {
  VirtTableReact,
  VirtFilterBuilderReact,
  type FilterModel,
  type VirtTableRef,
} from '@virt-table/react';

<VirtFilterBuilderReact
  tableRef={tableRef}      // 指向 <VirtTableReact> 的 ref:自动取列 + 自动应用
  value={model}            // 受控条件树
  autoApply={false}        // true 则每次改动即时生效
  onChange={setModel}      // 条件变化(含编辑中的半成品)
  onApply={onApply}        // 已应用到表格
  className="..." style={{ maxWidth: 900 }}
/>
ts
type Column = {
  filterType?: 'enum' | 'text' | 'number-range' | 'date-range'; // 也决定高级筛选的值域
  filterValueKind?: 'text' | 'number' | 'date' | 'enum' | 'boolean';
  filterOperators?: FilterOperator[];                  // 收窄该列可用算子
  filters?: Array<{ label: string; value: unknown }>;  // in/notIn 的候选来源
};

type Options = {
  filterModel?: FilterModel;                          // 初始条件树
  onFilterModelChange?: (model: FilterModel) => void;
};

// ref 方法
tableRef.current?.setFilterModel(model);
tableRef.current?.getFilterModel();
tableRef.current?.clearFilterModel();
tableRef.current?.hasActiveFilters();

注意

构建器要拿到 vanilla 实例才能取列并应用筛选,因此需在表格挂载后再渲染它 (示例里用一个 ready 状态延后一帧)。列的 filterMethod 只参与 in / notIn

示例

微应用尚未挂载。

源码

点击查看源码
tsx
import React from 'react';
import {
  VirtTableReact,
  VirtFilterBuilderReact,
  type ReactTableColumn,
  type VirtTableRef,
  type FilterModel,
  type VirtFilterBuilderHandle,
  vtColumnFilter,
} from '@virt-table/react';
import { faker } from '@faker-js/faker';

const departments = ['工程部', '设计部', '市场部', '财务部', '人事部'];
const levels = ['P5', 'P6', 'P7', 'P8'];
const cities = ['北京', '上海', '杭州', '深圳'];

type Row = {
  id: number;
  name: string;
  department: string;
  level: string;
  city: string;
  score: number;
  hiredAt: string;
};

const list: Row[] = Array.from({ length: 2000 }, (_, i) => ({
  id: i,
  name: faker.person.fullName(),
  department: departments[i % departments.length]!,
  level: levels[i % levels.length]!,
  city: cities[i % cities.length]!,
  score: Math.floor(Math.random() * 100),
  hiredAt: faker.date.between({ from: '2019-01-01', to: '2025-06-30' }).toISOString().slice(0, 10),
}));

const columns: ReactTableColumn<Row>[] = [
  { key: 'name', title: '姓名', width: 180, filterType: 'text' },
  {
    key: 'department',
    title: '部门',
    width: 130,
    filters: departments.map((d) => ({ label: d, value: d })),
  },
  { key: 'level', title: '级别', width: 100, filters: levels.map((l) => ({ label: l, value: l })) },
  {
    // 表头快捷筛选与高级筛选并存(AND)
    key: 'city',
    title: '城市',
    width: 110,
    filters: cities.map((c) => ({ label: c, value: c })),
  },
  { key: 'score', title: '分数', width: 100, filterType: 'number-range' },
  { key: 'hiredAt', title: '入职日期', width: 140, filterType: 'date-range' },
];

export default function FilterBuilderTable() {
  const tableRef = React.useRef<VirtTableRef>(null);
  const builderRef = React.useRef<VirtFilterBuilderHandle | null>(null);
  const [ready, setReady] = React.useState(false);
  const [autoApply, setAutoApply] = React.useState(false);
  const [model, setModel] = React.useState<FilterModel>(null);
  const [applyTick, setApplyTick] = React.useState(0);
  const [status, setStatus] = React.useState('');

  const updateStatus = React.useCallback(() => {
    const t = tableRef.current?.getTable() as
      | { core: { state: { renderTotal: number } } }
      | null
      | undefined;
    const rendered = t?.core?.state?.renderTotal ?? list.length;
    const headerFilters = Object.keys(tableRef.current?.getActiveFilters() ?? {});
    setStatus(
      `结果 ${rendered} / ${list.length} 行 · 高级筛选:` +
        `${tableRef.current?.getFilterModel() ? '已启用' : '未启用'}` +
        (headerFilters.length ? ` · 表头筛选:${headerFilters.join('、')}` : ''),
    );
  }, []);

  // 表格挂载后构建器才能拿到实例(取列 + 应用筛选)
  React.useEffect(() => {
    setReady(true);
    updateStatus();
  }, [updateStatus]);

  // 预设条件写入构建器后再应用(子组件的同步 effect 先于本 effect 执行)
  React.useEffect(() => {
    if (applyTick > 0) builderRef.current?.apply();
  }, [applyTick]);

  function loadPreset(): void {
    setApplyTick((t) => t + 1);
    setModel({
      kind: 'group',
      logic: 'and',
      children: [
        { kind: 'condition', colKey: 'score', operator: 'gte', value: 80 },
        {
          kind: 'group',
          logic: 'or',
          children: [
            { kind: 'condition', colKey: 'department', operator: 'in', value: ['工程部', '设计部'] },
            { kind: 'condition', colKey: 'level', operator: 'eq', value: 'P8' },
          ],
        },
      ],
    });
  }

  return (
    <div className="demo-wrapper">
      <h3 className="demo-title">React 高级筛选(筛选管理器)</h3>
      <div className="virt-table-controls">
        <label>
          <input
            type="checkbox"
            checked={autoApply}
            onChange={(e) => setAutoApply(e.target.checked)}
          />
          即时应用
        </label>
        <button
          type="button"
          className="virt-table-btn virt-table-btn-primary"
          onClick={loadPreset}
        >
          载入示例条件
        </button>
      </div>
      {/* 条件构建器渲染在表格之外 */}
      {ready && (
        <VirtFilterBuilderReact
          ref={builderRef}
          tableRef={tableRef}
          value={model}
          autoApply={autoApply}
          style={{ maxWidth: 900, marginBottom: 10 }}
          onChange={setModel}
          onApply={updateStatus}
        />
      )}
      <div className="status-text">{status}</div>
      <div style={{ width: 900, height: 520 }} className="demo-container">
        <VirtTableReact
          ref={tableRef}
          columns={columns}
          options={{
            list,
            itemKey: 'id',
            plugins: [vtColumnFilter()],
            estimatedSize: 40,
            buffer: 4,
            border: true,
            onFilterChange: updateStatus,
            onFilterModelChange: updateStatus,
          }}
        />
      </div>
    </div>
  );
}