Skip to content

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

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

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

使用的 API

vue
<VirtFilterBuilderVue
  v-model:model="model"    <!-- 条件树双向绑定 -->
  :table="tableInstance"   <!-- tableRef.getTable() 的结果:自动取列 + 自动应用 -->
  :auto-apply="autoApply"  <!-- true 则每次改动即时生效 -->
  @change="onChange"
  @apply="onApply"
/>
ts
import { VirtTableVue, VirtFilterBuilderVue, type FilterModel } from '@virt-table/vue';

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.value?.setFilterModel(model);
tableRef.value?.getFilterModel();
tableRef.value?.clearFilterModel();
tableRef.value?.hasActiveFilters();

// 构建器 ref 方法:getModel / setModel / setColumns / refresh / apply / clear

注意

构建器要拿到 vanilla 实例才能取列并应用筛选,因此在 onMounted 里用 tableRef.value?.getTable() 取实例后再传入。列的 filterMethod 只参与 in / notIn

示例

微应用尚未挂载。

源码

点击查看源码
vue
<template>
  <div class="demo-wrapper">
    <h3 class="demo-title">Vue 高级筛选(筛选管理器)</h3>
    <div class="virt-table-controls">
      <label>
        <input v-model="autoApply" type="checkbox" /> 即时应用
      </label>
      <button type="button" class="virt-table-btn virt-table-btn-primary" @click="loadPreset">载入示例条件</button>
    </div>
    <!-- 条件构建器在表格外部;v-model:model 双向绑定条件树 -->
    <div style="max-width: 900px; margin-bottom: 10px">
      <VirtFilterBuilderVue
        ref="builderRef"
        v-model:model="model"
        :table="tableInstance"
        :auto-apply="autoApply"
        @apply="updateStatus"
      />
    </div>
    <div class="status-text">{{ status }}</div>
    <div style="width: 900px; height: 520px" class="demo-container">
      <VirtTableVue ref="tableRef" :columns="columns" :options="options" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, nextTick, onMounted } from 'vue';
import {
  VirtTableVue,
  VirtFilterBuilderVue,
  type VueTableColumn,
  type FilterModel,
  vtColumnFilter,
} from '@virt-table/vue';
import { faker } from '@faker-js/faker';

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

const list = 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: VueTableColumn[] = [
  { 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' },
];

type TableInstance = {
  getTable: () => any;
  getFilterModel: () => FilterModel;
  getActiveFilters: () => Record<string, unknown[]>;
};

const tableRef = ref<TableInstance | null>(null);
const builderRef = ref<{ apply: () => void } | null>(null);
const tableInstance = ref<any>(null);
const autoApply = ref(false);
const model = ref<FilterModel>(null);
const status = ref('');

const options = {
  list,
  itemKey: 'id',
  plugins: [vtColumnFilter()],
  estimatedSize: 40,
  buffer: 4,
  border: true,
  onFilterChange: () => updateStatus(),
  onFilterModelChange: () => updateStatus(),
};

function updateStatus(): void {
  const t = tableRef.value?.getTable();
  const rendered = t?.core?.state?.renderTotal ?? list.length;
  const headerFilters = Object.keys(tableRef.value?.getActiveFilters() ?? {});
  status.value =
    `结果 ${rendered} / ${list.length} 行 · 高级筛选:${tableRef.value?.getFilterModel() ? '已启用' : '未启用'}` +
    (headerFilters.length ? ` · 表头筛选:${headerFilters.join('、')}` : '');
}

onMounted(() => {
  // 构建器需要 vanilla 实例才能自动取列并应用筛选
  tableInstance.value = tableRef.value?.getTable() ?? null;
  updateStatus();
});

async function loadPreset(): Promise<void> {
  model.value = {
    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' },
        ],
      },
    ],
  };
  // 等 watch 把新模型写进构建器后再应用
  await nextTick();
  builderRef.value?.apply();
}
</script>