Appearance
BizQueryBuilder 高级筛选构造器
「字段 + 操作符 + 值」条件行组构造器:组内**满足全部(且)/ 满足任一(或)**切换,max-depth 控制嵌套子组层数;值控件按字段类型自动联动(输入 / 选择 / 多选 / 数字 / 数字区间 / 单日期 / 日期区间),操作符切换时控件与取值自动适配。v-model 输出结构化条件树 { logic, conditions, groups },支持服务端存档回填还原。日志检索、BI 报表、复杂列表查询的动态条件位标准件,与 BizSearchForm(固定字段)、BizFilterTags(已选回显)互补。
基础用法
fields 声明可查字段,条件行「字段 / 操作符 / 值」三段式;右侧实时查看 v-model 条件树。<script setup lang="ts">
/** 基础用法:字段 / 操作符 / 值三段式条件行,v-model 输出结构化条件树 */
import { ref } from 'vue'
import { BizQueryBuilder } from 'vue3-ep-biz-lib'
import type { BizQueryField, BizQueryGroup } from 'vue3-ep-biz-lib'
const fields: BizQueryField[] = [
{ key: 'orderNo', label: '订单号' },
{ key: 'product', label: '商品', type: 'select', options: [
{ label: '打印机', value: 'printer' },
{ label: '扫描仪', value: 'scanner' },
{ label: '投影仪', value: 'projector' },
] },
{ key: 'remark', label: '备注' },
]
const value = ref<BizQueryGroup>({ logic: 'and', conditions: [], groups: [] })
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<BizQueryBuilder v-model="value" :fields="fields" />
<div style="font-size: 12px; color: var(--el-text-color-secondary)">v-model:</div>
<pre style="margin: 0; padding: 8px 12px; background: var(--el-fill-color-light); border-radius: 4px; font-size: 12px">{{ JSON.stringify(value, null, 2) }}</pre>
</div>
</template>
显示代码
值控件联动
且
-
{
"logic": "and",
"conditions": [
{
"field": "amount",
"operator": "between",
"value": 1000,
"value2": 5000
},
{
"field": "payTime",
"operator": "between",
"value": [
"2026-09-01",
"2026-09-30"
]
}
],
"groups": []
}字段
type 决定值控件;between 时数字字段变双数字框、日期字段变区间选择;in / notIn 变多选;「为空」类操作符隐藏值控件。切换操作符跨形态时自动清值。<script setup lang="ts">
/** 值控件联动:数字 / 数字区间 / 单日期 / 日期区间 / 多选,操作符切换自动换控件并清值 */
import { ref } from 'vue'
import { BizQueryBuilder } from 'vue3-ep-biz-lib'
import type { BizQueryField, BizQueryGroup } from 'vue3-ep-biz-lib'
const fields: BizQueryField[] = [
{ key: 'amount', label: '金额', type: 'number' },
{ key: 'count', label: '数量区间', type: 'numberrange' },
{ key: 'createdAt', label: '创建日期', type: 'date' },
{ key: 'payTime', label: '支付时间', type: 'daterange' },
{ key: 'channel', label: '渠道', type: 'select', options: [
{ label: '线上商城', value: 'online' },
{ label: '门店', value: 'offline' },
{ label: '分销', value: 'partner' },
] },
]
const value = ref<BizQueryGroup>({
logic: 'and',
conditions: [
{ field: 'amount', operator: 'between', value: 1000, value2: 5000 },
{ field: 'payTime', operator: 'between', value: ['2026-09-01', '2026-09-30'] },
],
groups: [],
})
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<BizQueryBuilder v-model="value" :fields="fields" />
<pre style="margin: 0; padding: 8px 12px; background: var(--el-fill-color-light); border-radius: 4px; font-size: 12px">{{ JSON.stringify(value, null, 2) }}</pre>
</div>
</template>
显示代码
嵌套分组
或
且
max-depth 控制子组层数(默认 1 层,0 不允许嵌套);组头切换且/或。本例预填「金额 ≥ 10000 或 (标签含 A 且 等级 = VIP)」。<script setup lang="ts">
/** 嵌套分组:maxDepth 控制层数,组内且/或切换;本例「金额条件 或 (标签含A 且 等级为VIP)」 */
import { ref } from 'vue'
import { BizQueryBuilder } from 'vue3-ep-biz-lib'
import type { BizQueryField, BizQueryGroup } from 'vue3-ep-biz-lib'
const fields: BizQueryField[] = [
{ key: 'amount', label: '金额', type: 'number' },
{ key: 'tag', label: '标签' },
{ key: 'level', label: '等级', type: 'select', options: [
{ label: 'VIP', value: 'vip' },
{ label: '普通', value: 'normal' },
] },
{ key: 'area', label: '区域' },
]
const value = ref<BizQueryGroup>({
logic: 'or',
conditions: [{ field: 'amount', operator: 'gte', value: 10000 }],
groups: [
{
logic: 'and',
conditions: [
{ field: 'tag', operator: 'contains', value: 'A' },
{ field: 'level', operator: 'eq', value: 'vip' },
],
},
],
})
</script>
<template>
<BizQueryBuilder v-model="value" :fields="fields" :max-depth="2" />
</template>
显示代码
检索场景
筛选条件
命中 4 条:无条件
条件树本地求值过滤日志(真实项目把条件树转查询参数 / DSL 交给后端);
operators 可收窄单字段操作符。<script setup lang="ts">
/** 检索场景:条件树拍平后本地过滤(真实项目把条件树转查询参数交给后端即可) */
import { computed, ref } from 'vue'
import { ElButton, ElTable, ElTableColumn, ElTag } from 'element-plus'
import { BizQueryBuilder } from 'vue3-ep-biz-lib'
import { BIZ_QUERY_OPERATOR_LABELS } from 'vue3-ep-biz-lib'
import type { BizQueryCondition, BizQueryField, BizQueryGroup } from 'vue3-ep-biz-lib'
interface LogRow {
id: number
user: string
module: string
level: 'info' | 'warn' | 'error'
message: string
day: string
}
const allLogs: LogRow[] = Array.from({ length: 12 }, (_, i) => ({
id: i + 1,
user: `admin${(i % 3) + 1}`,
module: ['登录', '订单', '库存'][i % 3],
level: (['info', 'warn', 'error'] as const)[i % 3],
message: `操作描述 ${i + 1}:包含关键字 abc`,
day: `2026-09-${String((i % 27) + 1).padStart(2, '0')}`,
}))
const fields: BizQueryField[] = [
{ key: 'user', label: '操作人' },
{ key: 'module', label: '模块', type: 'select', options: [
{ label: '登录', value: '登录' },
{ label: '订单', value: '订单' },
{ label: '库存', value: '库存' },
] },
{ key: 'level', label: '级别', type: 'select', operators: ['eq', 'ne', 'isNull'], options: [
{ label: 'info', value: 'info' },
{ label: 'warn', value: 'warn' },
{ label: 'error', value: 'error' },
] },
{ key: 'day', label: '日期', type: 'date' },
]
const value = ref<BizQueryGroup>({ logic: 'and', conditions: [{ field: 'level', operator: 'eq', value: 'error' }], groups: [] })
const builderRef = ref<{ getFlatConditions: () => BizQueryCondition[] }>()
/** 单条条件求值(演示本地过滤;后端检索时把条件树转 SQL/DSL 即可) */
const matchCondition = (row: LogRow, cond: BizQueryCondition): boolean => {
const raw = row[cond.field as keyof LogRow]
const target = String(raw ?? '')
const source = Array.isArray(cond.value) ? cond.value.map(String) : cond.value
switch (cond.operator) {
case 'eq': return target === String(source)
case 'ne': return target !== String(source)
case 'contains': return target.includes(String(source))
case 'notContains': return !target.includes(String(source))
case 'startsWith': return target.startsWith(String(source))
case 'endsWith': return target.endsWith(String(source))
case 'isNull': return target === ''
case 'isNotNull': return target !== ''
default: return true
}
}
const filtered = computed<LogRow[]>(() => {
const conds = value.value.conditions
const match = (row: LogRow): boolean =>
value.value.logic === 'and' ? conds.every((c) => matchCondition(row, c)) : conds.some((c) => matchCondition(row, c))
return conds.length === 0 ? allLogs : allLogs.filter(match)
})
const flatText = computed(() =>
(builderRef.value?.getFlatConditions() ?? [])
.map((c) => `${fields.find((f) => f.key === c.field)?.label} ${BIZ_QUERY_OPERATOR_LABELS[c.operator]} ${Array.isArray(c.value) ? c.value.join(' ~ ') : String(c.value ?? '空')}`)
.join(';') || '无条件'
)
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<BizQueryBuilder ref="builderRef" v-model="value" :fields="fields" :max-depth="0" />
<div style="display: flex; align-items: center; gap: 8px">
<ElButton size="small" @click="value = { logic: 'and', conditions: [], groups: [] }">清空条件</ElButton>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">命中 {{ filtered.length }} 条:{{ flatText }}</span>
</div>
<ElTable :data="filtered" size="small" max-height="260" border>
<ElTableColumn prop="day" label="日期" width="110" />
<ElTableColumn prop="user" label="操作人" width="90" />
<ElTableColumn prop="module" label="模块" width="80" />
<ElTableColumn label="级别" width="80">
<template #default="{ row }">
<ElTag :type="row.level === 'error' ? 'danger' : row.level === 'warn' ? 'warning' : 'info'" size="small">{{ row.level }}</ElTag>
</template>
</ElTableColumn>
<ElTableColumn prop="message" label="描述" min-width="180" />
</ElTable>
</div>
</template>
显示代码
回填与编程式
且至
v-model 预置条件树还原 UI;
clear() 清空、getFlatConditions() 拍平全部条件行便于提交。<script setup lang="ts">
/** 回填与编程式:v-model 预置条件树还原 UI;expose clear / getFlatConditions */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizQueryBuilder } from 'vue3-ep-biz-lib'
import type { BizQueryBuilderInstance, BizQueryField, BizQueryGroup } from 'vue3-ep-biz-lib'
const fields: BizQueryField[] = [
{ key: 'title', label: '标题' },
{ key: 'author', label: '作者' },
{ key: 'views', label: '阅读量', type: 'number' },
]
/** 服务端存档的「我的常用检索」直接回填 */
const saved = { logic: 'and', conditions: [
{ field: 'title', operator: 'contains', value: '发布' },
{ field: 'views', operator: 'between', value: 500, value2: 5000 },
], groups: [] } satisfies BizQueryGroup
const value = ref<BizQueryGroup>(JSON.parse(JSON.stringify(saved)) as BizQueryGroup)
const builderRef = ref<BizQueryBuilderInstance>()
const showFlat = (): void => {
const flat = builderRef.value?.getFlatConditions() ?? []
ElMessage.info(`共 ${flat.length} 条条件,已拍平待提交`)
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px">
<BizQueryBuilder ref="builderRef" v-model="value" :fields="fields" />
<div style="display: flex; gap: 8px">
<ElButton size="small" @click="value = JSON.parse(JSON.stringify(saved)) as BizQueryGroup">回填存档条件</ElButton>
<ElButton size="small" @click="builderRef?.clear()">clear() 清空</ElButton>
<ElButton size="small" type="primary" @click="showFlat">getFlatConditions()</ElButton>
</div>
</div>
</template>
显示代码
简洁模式
筛选条件
暂无筛选条件,添加一条
max-depth=0 时组头不出现「添加分组」,条件行数量不受限
max-depth=0 禁止嵌套分组,轻量列表只保留「字段 / 操作符 / 值」条件行。<script setup lang="ts">
/** 简洁模式:max-depth=0 禁止嵌套分组,轻量列表只留「字段 + 操作符 + 值」条件行 */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizQueryBuilder } from 'vue3-ep-biz-lib'
import type { BizQueryField, BizQueryGroup } from 'vue3-ep-biz-lib'
const fields: BizQueryField[] = [
{ key: 'title', label: '标题' },
{ key: 'creator', label: '创建人' },
{
key: 'level',
label: '优先级',
type: 'select',
options: [
{ label: 'P0 紧急', value: 'P0' },
{ label: 'P1 高', value: 'P1' },
{ label: 'P2 中', value: 'P2' },
],
},
]
const value = ref<BizQueryGroup>({ logic: 'and', conditions: [], groups: [] })
const onSearch = (): void => {
ElMessage.success(`提交 ${value.value.conditions.length} 个条件(无嵌套组)`)
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px; max-width: 720px">
<BizQueryBuilder v-model="value" :fields="fields" :max-depth="0" />
<div>
<ElButton size="small" type="primary" @click="onSearch">查询</ElButton>
<span style="margin-left: 12px; font-size: 12px; color: var(--el-text-color-secondary)">
max-depth=0 时组头不出现「添加分组」,条件行数量不受限
</span>
</div>
</div>
</template>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| fields | BizQueryField[] | —(必填) | 可选字段配置 |
| v-model | BizQueryGroup | 空条件树 | 结构化条件树,变更即同步 |
| max-depth | number | 1 | 嵌套子组层数上限(0 = 不允许嵌套) |
BizQueryField
| 属性 | 类型 | 说明 |
|---|---|---|
| key | string | 字段标识 |
| label | string | 显示名 |
| type | 'input' | 'select' | 'number' | 'date' | 'daterange' | 'numberrange' | 值控件类型(默认 input) |
| options | { label; value }[] | select 候选项 |
| operators | BizQueryOperator[] | 允许的操作符(缺省按 type 推导) |
| placeholder | string | 值控件占位提示 |
BizQueryGroup
| 属性 | 类型 | 说明 |
|---|---|---|
| logic | 'and' | 'or' | 组内连接词 |
| conditions | BizQueryCondition[] | 条件行 { field, operator, value, value2 } |
| groups | BizQueryGroup[] | 嵌套子组 |
内置操作符(BIZ_QUERY_OPERATOR_LABELS 可取中文文案):eq / ne / contains / notContains / startsWith / endsWith / gt / gte / lt / lte / between / in / notIn / isNull / isNotNull。
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| update:modelValue | (value: BizQueryGroup) | v-model 同步 |
| change | (value: BizQueryGroup) | 条件树任何变更 |
Expose
| 属性/方法 | 说明 |
|---|---|
| clear() | 清空全部条件 |
| getFlatConditions() | 拍平全部条件行(含嵌套组),用于提交 / 参数拼装 |