文档中心

从三分钟上手到内部运作原理 · 与仓库 docs/ 的 Markdown 文档同源

快速上手

三分钟跑通:安装 → 注册插件并注入 api → 用 FilterField 声明字段 → 收集 Criteria。

1. 安装

npm i smart-filter-vue
# peer 依赖(如尚未安装)
npm i vue@^3 element-plus@^2 @element-plus/icons-vue@^2

2. 注册插件,注入 api 适配器(全部可选)

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')

3. 声明字段,FilterField 渲染

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 适配器注入。

完整链路

FieldConfig字段配置(JSON)
FilterField按 componentType 分发
统一透传 props / 转发事件
字段组件交互(输入 / 下拉 /
魔法棒 / 面板)
CriteriaNodecriteria-change 输出
空值为 null
后端解析树生成 SQL
字段组件需要数据/AI 时
useSmartFilterApi()inject 取适配器
先判空,缺失优雅降级
api 适配器你的后端 / AI 服务
(Playground 为白名单 Mock)

魔法棒三态状态机(useMagicPanel)

带 AI 能力的组件(语义搜索 / 时间计算 / 数值计算)共用同一个状态机:

idle 待生成输入变化即回到此态;点击魔法棒:有输入则触发解析,无输入只弹面板
loading 解析中图标旋转;期间重复触发被忽略;解析请求带序列号,过期响应直接丢弃(防乱序覆盖)
generated 已生成图标蓝色发光;点击魔法棒开合面板;面板内可"重新生成/重新计算"兜底

失焦自动触发(§17.5 规则)

  • 仅 AI 对话类输入框适用:失焦 + 有文本 + 未生成 + 非解析中 → 自动触发一次解析,无需点魔法棒。
  • 解析失败:弹错误提示 + 抛出 ai-error 事件,状态回到待生成(不会误置"已生成")。
  • 规则/表达式输入不触发 AI:数值类组件失焦只做本地清理与同步。

AI 入口收窄原则(全库统一)

AI 能力强但不可控,规则能力弱但 100% 确定。整个库把两者的分工收敛为一句话:确定的归规则,模糊的归 AI。完整决策链如下:

  • 第一关:本地防呆解析。用户输入先进 parseSimpleComparison 五类规则(裸数字 / 前缀文字符号 / 后缀符号翻转 / 区间自动排序 / 全角兼容)——无歧义表达式(5>55~10大于5根本不到 AI,本地直接落地。为什么连裸数字"3"都不给大模型?实测老模型会把"3"脑补成"1992-1993 年出生"之类结果——无歧义输入走 AI 只会引入不确定性。
  • 第二关:对话 → 规则,唯一 AI 入口。只有第一关返回 null 的模糊自然语言("中青年""快退休了")才调用 timeParse / numberParse / semanticExpand。AI 只做"理解人话 → 产出结构化条件"这一件事。
  • 规则 → 对话:确定性本地转换,禁止调 AI。反向(算符+日期 → 文字)数学上是确定的,让大模型"看着日期写话"只会措辞失控——实测事故:面板算符是"小于 1995-07-27",AI 回填的对话文字却是"小于等于",文字与条件不一致比没有文字更糟糕。因此反向固定走 datesToRuleText 本地转换:措辞与算符 100% 一致、同步完成、无等待、无失败路径。

收益:AI 调用量最小化(省钱省时)、无 AI 时组件仍高度可用(本地预解析 + 规则/直接模式纯本地)、输出可预测(规则链路 100% 可测试)。五类规则表与换算细节见下方时间计算专题 §5。

时间计算专题(time_calc 内部运作)

time_calc 是本库最复杂的组件:同一个"年龄 ≥ 35"条件,要在自然语言、数值表达式、日历三种形态之间无损互换。本章讲清它的全部内部机制。

1. V / D 双视角与算符翻转

组件同时面对两个"字段":

视角字段例子说明
V 侧(虚拟字段)label,如"年龄"年龄 ≥ 35数据库不存数值,由日期推算
D 侧(真实字段)targetField,如"出生日期"出生日期 ≤ 1991-07数据库实际存储与查询的列

年龄越大 ⇔ 出生日期越早——两个视角的比较方向相反。视角切换(模式切换、面板显示、Criteria 输出)时必须翻转算符:

flipOperator: gt ↔ lt gte ↔ lte eq / between 不变

2. start / end 恒为 D 侧存储

组件内部只有一份规范值:{ start, end }永远是真实字段(D 侧)的日期区间。三种模式只是同一份 start/end 的三种"输入法"与"显示器":

  • AI 对话:文字 ⇄ start/end(正向 AI 解析;反向本地规则转换)
  • 规则:V 侧算符 + 数值 ⇄ start/end(calcDateByRule / calcNumberFromDate 换算)
  • 直接:日历 ⇄ start/end(直连)

因此模式切换零损耗:日期值保留,只翻转算符视角(expression ↔ 其余两模式 flip,ai_dialog ↔ direct 不变)。

三模式流转示意图

规范存储:start / end 恒为 D 侧(真实字段)日期区间 —— 切换零损耗,仅翻转算符视角 ai_dialog · AI 对话 搜索框 自然语言文本框 Info 面板 D 侧日历形式 (真实字段标签 + 算符 + 日历) expression · 规则 搜索框 V 侧算符 + 数值 Info 面板 D 侧日历形式 (算符符号 flip 后 D 侧显示) direct · 直接 搜索框 D 侧算符 + 日历 Info 面板 V 侧表达式(可编辑数值) (虚拟字段标签 + 算符 + 数值框) flip flip 不 flip(ai_dialog ↔ direct 同为 D 侧) 点击面板左上角模式名循环切换:ai_dialog → expression → direct → ai_dialog;切换不触发 AI

读图要点:三个框是同一批 start/end 的三种"输入法";面板永远显示与搜索框互补的另一侧(面板对调);expression 与另外两模式互切时算符 flip(V⇄D),ai_dialog 与 direct 互切不 flip(同侧)。

3. 规则换算:calcDateByRule(V 侧算符 + 数值 → D 侧日期区间)

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 便于测试。

4. 转换链路:AI 只有"对话 → 规则"一个入口

方向方式说明
对话 → 规则调 AI(timeParse)唯一 AI 入口:模糊自然语言 → 算符 + 日期
规则 → 对话本地确定性转换(datesToRuleText)算符 + 日期 → 文字,数学上确定,不调 AI

为什么规则→对话不能用 AI:反向解析让大模型"看着日期写话",措辞不可控——实测出现过面板算符是"小于 1995-07-27"、AI 回填文字却是"小于等于"的事故,文字与条件不一致比没有文字更糟糕。改为本地转换后,措辞与算符 100% 一致(lt→小于 / lte→小于等于 / gt→大于 / gte→大于等于 / eq→等于 / between→"在 X ~ Y 之间"),且同步完成、无等待、无失败路径。日历改动、算符切换、切入 AI 对话模式的文本回填全部走这条路。

5. 本地防呆解析:parseSimpleComparison 五类规则

为什么裸数字 "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 >5lt / 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~5between(value 小、value2 大)

其余输入("中青年""快退休了""大约 5")一律返回 null,交给 AI 兜底——语义清晰的分工:确定的归规则,模糊的归 AI

6. 面板对调设计

三种模式下,搜索框与魔法面板(Info 面板)各显示哪一侧,遵循"对调"原则——面板永远显示与搜索框互补的另一侧视角:

模式搜索框Info 面板
AI 对话自然语言文本框直接形式(D 侧):基准时间 + 真实字段标签行(出生日期)+ 可点算符符号(D 侧)+ 日历
规则V 侧算符 + 数值直接形式(D 侧):同上(算符符号显示 flip 后的 D 侧符号)
直接D 侧算符 + 日历规则形式(V 侧):虚拟字段标签(年龄)+ 可点算符符号(V 侧,flip 显示)+ 可编辑数值输入框(区间两个)

双向同步:面板改数值(回车/失焦)→ calcDateByRule 重算日期 → 顶部日历更新(非数字输入失焦清空,相同输入不重复计算);顶部改日期 → calcNumberFromDate 反算数值 → 面板更新。算符符号点击按算符列表顺序循环,切换时已有日期值自动迁移。

7. 算符配置化(operators)

默认 6 个算符 > ≥ < ≤ = ~(~ 即区间 between),搜索框下拉与面板符号循环共用同一份列表。可用 operators 收窄:

operators: ['gte', 'lte']                          // 字符串数组,label 按默认表映射
operators: [{ label: '≥', value: 'gte' }, { label: '≤', value: 'lte' }] // 对象数组

between(区间)是标准筛选能力,缺失时自动补入,无法排除。number_calc 的 operators 语义相同。

8. 三精度配置样例

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 协议

每个字段组件通过 criteria-change 输出一个节点(或 null),外层聚合成筛选树交给后端。节点结构扁平、自描述:

{
  field: 'age',               // 字段名
  componentType: 'time_calc',  // 组件类型
  operator: 'gte',           // 比较算符(time_calc 恒为 V 侧语义)
  value: 35,                 // 比较值(结构随 operator 而定)
  // 可选附加:targetField / fieldLabel / resolved(time_calc)
}

各组件标准节点

componentTypeoperatorvalue 结构
smart_optioneq / in / like / mixed字符串 / 数组 / like 文本 / { exact, fuzzy }
smart_option_singleeq / like字符串(精确选项值或 LIKE 文本)
text_inputlikestring[](LIKE 标签 + 输入框文字)
semantic_searchlike{ positive: [], negative: [] }
time_calcgt/gte/lt/lte/eq/between(V 侧)数值 / { start, end }(between);resolved 附带 D 侧日期区间
number_calcgt/gte/lt/lte/eq/between数值 / { start, end }(between)
boolean_switcheq标签字符串(如 '在职')
id_inputeq字符串

空值规则

  • 字段无有效输入时输出 null——该字段不参与筛选,外层聚合时跳过。
  • number_calc 的 between 缺上限视为无效,输出 null;time_calc 无日期值输出 null;boolean_switch "未设置"输出 null。

逐字段说明与兼容矩阵见 仓库 criteria-protocol.md →

api 适配器契约(5 函数)

组件需要的后端/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: [] }提示降级

错误路径

  • 错误分类:适配器 reject 的 Error.message 以「大模型识别失败」开头视为识别失败(原样展示);其余一律视为调用失败(网络/超时/5xx/未配置适配器,统一文案)。
  • 组件弹出错误提示 + 抛出 ai-error 事件(载荷为错误对象),魔法棒回到待生成状态;ai-loading 事件上报开始/结束。
  • 返回 null 或缺键:按空结果防御读取,不崩。
  • 无歧义简单表达式不会调用 timeParse / numberParse(本地预解析直接落地)。

Playground 的白名单 Mock(集成参考)

Playground 注入的就是一个纯前端白名单 Mock(模拟 300~600ms 延迟),可直接照抄到你的项目里做联调桩:

  • 联想:仅"张伟"系列片段;语义扩充:仅 Vue / 前端;时间:仅 10岁以上 等 3 条;数值:仅 80分以上 等 2 条;选项集:固定部门列表。
  • 白名单外输入一律 reject("大模型识别失败")——这是验证组件错误路径的最简方式。

完整签名表、REST 对接示例与 mock 全文见 仓库 api-adapter.md →

配置 Schema(FieldConfig)

FilterField 按字段配置对象渲染组件,核心键一览:

类型说明
fieldString字段名(必填)
labelString字段标签(行标签 / AI 语义参考)
componentTypeStringsmart_option / smart_option_single / text_input / semantic_search / time_calc / number_calc / boolean_switch / id_input
operatorString默认算符(time_calc / number_calc 等)
placeholderString占位符
optionsArraysmart_option 静态选项
optionSourceObject{ type: 'static' | 'distinct_scan' | 'api' };distinct_scan 经 getFieldOptions 加载,配置多出项置灰
targetField / targetFieldLabelStringtime_calc 真实日期列与标签(双标签联动)
componentPropsObject组件级配置(见下)

componentProps 常用键

组件说明
smart_optionmultiple / likeMode多选;multi_like / single_like / no_like
text_inputsearchable / suggestLimit / debounceMs / maxLength / selectable联想开关与参数
time_calcdateType / inputMode / locale / operators精度 year|month|date;初始模式;日历语言;算符列表(字符串数组或 {label,value} 数组,between 恒可用)
number_calcinputMode / operatorsexpression|ai_dialog;算符列表(同上)
boolean_switchtrueLabel / falseLabel / triState标签与三态开关

完整 Schema(含 summaryTemplate 等)见 仓库 configuration-schema.md →