从三分钟上手到内部运作原理 · 与仓库 docs/ 的 Markdown 文档同源
三分钟跑通:安装 → 注册插件并注入 api → 用 FilterField 声明字段 → 收集 Criteria。
npm i smart-filter-vue
# peer 依赖(如尚未安装)
npm i vue@^3 element-plus@^2 @element-plus/icons-vue@^2
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import SmartFilter from 'smart-filter-vue'
import 'smart-filter-vue/style.css'
createApp(App)
.use(ElementPlus)
.use(SmartFilter, {
api: {
// 数据库 LIKE 联想(text_input / semantic_search)
suggestFieldValues: async (field, keyword, { limit, contextBefore, contextAfter }) => ({ suggestions: [] }),
// smart_option distinct_scan 选项集
getFieldOptions: async (field) => ({ options: [] }),
// time_calc 模糊自然语言 → 日期范围(简单表达式组件本地解析,不会调到这里)
timeParse: async (text, fieldLabel, dateType) => ({ start: null, end: null, description: '' }),
// number_calc 模糊自然语言 → 算符+数值
numberParse: async (text, field, fieldLabel) => ({ operator: 'eq', value: undefined, value2: undefined }),
// semantic_search 关键词 → 正/反向词集
semanticExpand: async (keyword, field, fieldLabel) => ({ positive: [], negative: [] }),
},
})
.mount('#app')
const fields = [
{ field: 'name', label: '姓名', componentType: 'text_input' },
{ field: 'age', label: '年龄', componentType: 'time_calc',
operator: 'gte', targetField: 'birth_date', targetFieldLabel: '出生日期',
componentProps: { dateType: 'month' } },
]
<FilterField
v-for="f in fields" :key="f.field"
v-model="values[f.field]" :field="f"
@criteria-change="onCriteria(f.field, $event)"
/>
每个字段的每次有效输入都会产出一个 Criteria 节点(空值为 null),聚合成树交给后端即可。仓库完整版 →
库本身不内置任何 HTTP 请求:交互逻辑在组件内,数据/AI 能力全部经 api 适配器注入。
带 AI 能力的组件(语义搜索 / 时间计算 / 数值计算)共用同一个状态机:
ai-error 事件,状态回到待生成(不会误置"已生成")。AI 能力强但不可控,规则能力弱但 100% 确定。整个库把两者的分工收敛为一句话:确定的归规则,模糊的归 AI。完整决策链如下:
parseSimpleComparison 五类规则(裸数字 / 前缀文字符号 / 后缀符号翻转 / 区间自动排序 / 全角兼容)——无歧义表达式(5、>5、5~10、大于5)根本不到 AI,本地直接落地。为什么连裸数字"3"都不给大模型?实测老模型会把"3"脑补成"1992-1993 年出生"之类结果——无歧义输入走 AI 只会引入不确定性。datesToRuleText 本地转换:措辞与算符 100% 一致、同步完成、无等待、无失败路径。收益:AI 调用量最小化(省钱省时)、无 AI 时组件仍高度可用(本地预解析 + 规则/直接模式纯本地)、输出可预测(规则链路 100% 可测试)。五类规则表与换算细节见下方时间计算专题 §5。
time_calc 是本库最复杂的组件:同一个"年龄 ≥ 35"条件,要在自然语言、数值表达式、日历三种形态之间无损互换。本章讲清它的全部内部机制。
组件同时面对两个"字段":
| 视角 | 字段 | 例子 | 说明 |
|---|---|---|---|
| V 侧(虚拟字段) | label,如"年龄" | 年龄 ≥ 35 | 数据库不存数值,由日期推算 |
| D 侧(真实字段) | targetField,如"出生日期" | 出生日期 ≤ 1991-07 | 数据库实际存储与查询的列 |
年龄越大 ⇔ 出生日期越早——两个视角的比较方向相反。视角切换(模式切换、面板显示、Criteria 输出)时必须翻转算符:
flipOperator: gt ↔ lt gte ↔ lte eq / between 不变
组件内部只有一份规范值:{ start, end },永远是真实字段(D 侧)的日期区间。三种模式只是同一份 start/end 的三种"输入法"与"显示器":
因此模式切换零损耗:日期值保留,只翻转算符视角(expression ↔ 其余两模式 flip,ai_dialog ↔ direct 不变)。
读图要点:三个框是同一批 start/end 的三种"输入法";面板永远显示与搜索框互补的另一侧(面板对调);expression 与另外两模式互切时算符 flip(V⇄D),ai_dialog 与 direct 互切不 flip(同侧)。
以 dateType='date'、基准日 2026-07-20 为例(month/year 精度同理,边界按对应精度取整):
| V 侧条件 | D 侧结果 | 边界规则 |
|---|---|---|
| 年龄 > 30(gt) | end = 1996-07-19 | 严格大于:边界再早一个精度单位 |
| 年龄 ≥ 30(gte) | end = 1996-07-20 | 含边界 |
| 年龄 < 30(lt) | start = 1996-07-21 | 严格小于:边界再晚一个精度单位 |
| 年龄 ≤ 30(lte) | start = 1996-07-20 | 含边界 |
| 年龄 = 30(eq) | start = 1995-07-21, end = 1996-07-20 | 左开右闭区间:出生在这一天区间内恰好满 30 岁 |
| 年龄 30~40(between) | start = 1986-07-20, end = 1996-07-20 | 上限对应更早的日期(start),下限对应更晚的日期(end) |
反向换算 calcNumberFromDate(dateStr, dateType, today) 按 dateType 精度完整比较年月日:month 精度下"本月生日未到"减一,date 精度下"今天生日未到"减一。这些纯函数全部从包导出,支持注入 today 便于测试。
| 方向 | 方式 | 说明 |
|---|---|---|
| 对话 → 规则 | 调 AI(timeParse) | 唯一 AI 入口:模糊自然语言 → 算符 + 日期 |
| 规则 → 对话 | 本地确定性转换(datesToRuleText) | 算符 + 日期 → 文字,数学上确定,不调 AI |
为什么规则→对话不能用 AI:反向解析让大模型"看着日期写话",措辞不可控——实测出现过面板算符是"小于 1995-07-27"、AI 回填文字却是"小于等于"的事故,文字与条件不一致比没有文字更糟糕。改为本地转换后,措辞与算符 100% 一致(lt→小于 / lte→小于等于 / gt→大于 / gte→大于等于 / eq→等于 / between→"在 X ~ Y 之间"),且同步完成、无等待、无失败路径。日历改动、算符切换、切入 AI 对话模式的文本回填全部走这条路。
为什么裸数字 "3" 不能交给大模型:实测老模型会把 "3" 脑补成"1992-1993 年出生"之类的结果——无歧义输入走 AI 只会引入不确定性。因此 AI 对话输入先做本地规则预解析(timeCalcCore.tryLocalTimeParse = parseSimpleComparison + calcDateByRule),命中直接落地(提示"规则解析完成"),未命中才调 timeParse。number_calc 同理(parseSimpleComparison 直接产出算符+数值)。
| 类别 | 例子 | 结果 |
|---|---|---|
| 裸数字 | 5 3.5 30岁 | eq 5 |
| 前缀符号(含全角) | <5 >5 =5 ≤5 ≥5 >5 | lt / gt / eq / lte / gte |
| 前缀文字(岁/年可选) | 大于5 大于等于5岁 超过5 不足5 | 对应算符 |
| 后缀符号(语义翻转,防呆重点) | 5<→gt 5>→lt 5≥→lte 5≤→gte 5=→eq | "5<" 读作 "5<X",即 X>5 |
| 区间(自动排序) | 5~10 5-10 5—10 5到10 5至10 10~5 | between(value 小、value2 大) |
其余输入("中青年""快退休了""大约 5")一律返回 null,交给 AI 兜底——语义清晰的分工:确定的归规则,模糊的归 AI。
三种模式下,搜索框与魔法面板(Info 面板)各显示哪一侧,遵循"对调"原则——面板永远显示与搜索框互补的另一侧视角:
| 模式 | 搜索框 | Info 面板 |
|---|---|---|
| AI 对话 | 自然语言文本框 | 直接形式(D 侧):基准时间 + 真实字段标签行(出生日期)+ 可点算符符号(D 侧)+ 日历 |
| 规则 | V 侧算符 + 数值 | 直接形式(D 侧):同上(算符符号显示 flip 后的 D 侧符号) |
| 直接 | D 侧算符 + 日历 | 规则形式(V 侧):虚拟字段标签(年龄)+ 可点算符符号(V 侧,flip 显示)+ 可编辑数值输入框(区间两个) |
双向同步:面板改数值(回车/失焦)→ calcDateByRule 重算日期 → 顶部日历更新(非数字输入失焦清空,相同输入不重复计算);顶部改日期 → calcNumberFromDate 反算数值 → 面板更新。算符符号点击按算符列表顺序循环,切换时已有日期值自动迁移。
默认 6 个算符 > ≥ < ≤ = ~(~ 即区间 between),搜索框下拉与面板符号循环共用同一份列表。可用 operators 收窄:
operators: ['gte', 'lte'] // 字符串数组,label 按默认表映射
operators: [{ label: '≥', value: 'gte' }, { label: '≤', value: 'lte' }] // 对象数组
between(区间)是标准筛选能力,缺失时自动补入,无法排除。number_calc 的 operators 语义相同。
year(年)——只需粗粒度年龄段筛选(如"40 岁以上"),日历为年份选择器,日期值 YYYY:
{
field: 'age', label: '年龄', componentType: 'time_calc',
operator: 'gte',
targetField: 'birth_date', targetFieldLabel: '出生年份',
componentProps: { dateType: 'year', inputMode: 'ai_dialog' },
}
month(年月,默认)——常规人事/运营筛选(如"司龄 5 年以上"),日期值 YYYY-MM:
{
field: 'hire_years', label: '司龄', componentType: 'time_calc',
operator: 'gte',
targetField: 'hire_date', targetFieldLabel: '入职年月',
componentProps: { dateType: 'month', inputMode: 'expression', operators: ['gte', 'lte', 'between'] },
}
date(年月日)——需要精确到日的场景(如"试用期 90 天内到期"),日历为日期选择器,日期值 YYYY-MM-DD;严格算符边界平移到"日":
{
field: 'age', label: '年龄', componentType: 'time_calc',
operator: 'gte',
targetField: 'birth_date', targetFieldLabel: '出生日期',
componentProps: { dateType: 'date', inputMode: 'ai_dialog', locale: 'zh' },
}
三个样例的在线交互见 Playground 的三张时间计算卡片;dateType 一旦上线不要轻易变更——同一字段混用多种精度会让既有条件摘要不可比。
每个字段组件通过 criteria-change 输出一个节点(或 null),外层聚合成筛选树交给后端。节点结构扁平、自描述:
{
field: 'age', // 字段名
componentType: 'time_calc', // 组件类型
operator: 'gte', // 比较算符(time_calc 恒为 V 侧语义)
value: 35, // 比较值(结构随 operator 而定)
// 可选附加:targetField / fieldLabel / resolved(time_calc)
}
| componentType | operator | value 结构 |
|---|---|---|
| smart_option | eq / in / like / mixed | 字符串 / 数组 / like 文本 / { exact, fuzzy } |
| smart_option_single | eq / like | 字符串(精确选项值或 LIKE 文本) |
| text_input | like | string[](LIKE 标签 + 输入框文字) |
| semantic_search | like | { positive: [], negative: [] } |
| time_calc | gt/gte/lt/lte/eq/between(V 侧) | 数值 / { start, end }(between);resolved 附带 D 侧日期区间 |
| number_calc | gt/gte/lt/lte/eq/between | 数值 / { start, end }(between) |
| boolean_switch | eq | 标签字符串(如 '在职') |
| id_input | eq | 字符串 |
null——该字段不参与筛选,外层聚合时跳过。逐字段说明与兼容矩阵见 仓库 criteria-protocol.md →
组件需要的后端/AI 能力全部以函数形式注入,全部可选、缺失优雅降级、永不抛出未捕获异常。
| 函数 | 签名 | 返回 | 未配置时 |
|---|---|---|---|
suggestFieldValues | (field, keyword, { limit, contextBefore, contextAfter }) | { suggestions: [] } | 不弹联想下拉 |
getFieldOptions | (field) | { options: [] } | distinct_scan 退化为仅配置选项 |
timeParse | (text, fieldLabel, dateType) | { start, end, description } | 模糊语义提示降级(本地预解析/规则/直接模式不受影响) |
numberParse | (text, field, fieldLabel) | { operator, value, value2? } | 同上 |
semanticExpand | (keyword, field, fieldLabel) | { positive: [], negative: [] } | 提示降级 |
Error.message 以「大模型识别失败」开头视为识别失败(原样展示);其余一律视为调用失败(网络/超时/5xx/未配置适配器,统一文案)。ai-error 事件(载荷为错误对象),魔法棒回到待生成状态;ai-loading 事件上报开始/结束。Playground 注入的就是一个纯前端白名单 Mock(模拟 300~600ms 延迟),可直接照抄到你的项目里做联调桩:
Vue / 前端;时间:仅 10岁以上 等 3 条;数值:仅 80分以上 等 2 条;选项集:固定部门列表。完整签名表、REST 对接示例与 mock 全文见 仓库 api-adapter.md →
FilterField 按字段配置对象渲染组件,核心键一览:
| 键 | 类型 | 说明 |
|---|---|---|
field | String | 字段名(必填) |
label | String | 字段标签(行标签 / AI 语义参考) |
componentType | String | smart_option / smart_option_single / text_input / semantic_search / time_calc / number_calc / boolean_switch / id_input |
operator | String | 默认算符(time_calc / number_calc 等) |
placeholder | String | 占位符 |
options | Array | smart_option 静态选项 |
optionSource | Object | { type: 'static' | 'distinct_scan' | 'api' };distinct_scan 经 getFieldOptions 加载,配置多出项置灰 |
targetField / targetFieldLabel | String | time_calc 真实日期列与标签(双标签联动) |
componentProps | Object | 组件级配置(见下) |
| 组件 | 键 | 说明 |
|---|---|---|
| smart_option | multiple / likeMode | 多选;multi_like / single_like / no_like |
| text_input | searchable / suggestLimit / debounceMs / maxLength / selectable | 联想开关与参数 |
| time_calc | dateType / inputMode / locale / operators | 精度 year|month|date;初始模式;日历语言;算符列表(字符串数组或 {label,value} 数组,between 恒可用) |
| number_calc | inputMode / operators | expression|ai_dialog;算符列表(同上) |
| boolean_switch | trueLabel / falseLabel / triState | 标签与三态开关 |
完整 Schema(含 summaryTemplate 等)见 仓库 configuration-schema.md →