SearchForm

View source
Schema 驱动的可折叠搜索表单组件

简介

MSearchForm 是一个 Schema 驱动的搜索表单组件,内置网格布局、搜索/重置按钮和折叠行为。当搜索项较多时,超出可见行数的字段会自动折叠,用户可以点击展开/收起按钮查看全部搜索项。

复用 AutoForm 的底层基础设施(schema 内省、控件映射、字段渲染器),支持通过 Zod schema 定义搜索字段。

基础用法

最简单的搜索表单,3 列布局,超出 1 行的字段自动折叠:

<script setup lang="ts">
const { afz } = useAutoForm()

const schema = afz.object({
  name: afz.string({ controlProps: { placeholder: '请输入姓名' } }).meta({ label: '姓名' }).optional(),
  status: afz.enum(['启用', '禁用', '待审核']).meta({ label: '状态' }).optional(),
  department: afz.string({ controlProps: { placeholder: '请输入部门' } }).meta({ label: '部门' }).optional(),
  keyword: afz.string({ controlProps: { placeholder: '请输入关键词' } }).meta({ label: '关键词' }).optional(),
  email: afz.email({ controlProps: { placeholder: '请输入邮箱' } }).meta({ label: '邮箱' }).optional()
})

const params = ref({})
const result = ref('')

function handleSearch(value: Record<string, unknown>) {
  result.value = JSON.stringify(value, null, 2)
}

function handleReset() {
  result.value = ''
}
</script>

<template>
  <div class="space-y-4">
    <MSearchForm
      v-model="params"
      :schema="schema"
      @search="handleSearch"
      @reset="handleReset"
    />
    <pre v-if="result" class="text-sm bg-muted p-3 rounded-(--ui-radius)">{{ result }}</pre>
  </div>
</template>

v-model 绑定

通过 v-model 双向绑定表单数据。传入的初始值会被组件记录为重置基准——点击「重置」按钮时恢复到初始值而非清空:

{
  "name": "张三",
  "status": "启用"
}
<script setup lang="ts">
const { afz } = useAutoForm()

const schema = afz.object({
  name: afz.string({ controlProps: { placeholder: '请输入姓名' } }).meta({ label: '姓名' }).optional(),
  status: afz.enum(['启用', '禁用', '待审核']).meta({ label: '状态' }).optional(),
  keyword: afz.string({ controlProps: { placeholder: '请输入关键词' } }).meta({ label: '关键词' }).optional()
})

const params = ref<Record<string, unknown>>({
  name: '张三',
  status: '启用'
})
</script>

<template>
  <div class="space-y-4">
    <MSearchForm v-model="params" :schema="schema" />
    <pre class="text-sm bg-muted p-3 rounded-(--ui-radius)">{{ JSON.stringify(params, null, 2) }}</pre>
    <UButton
      size="sm"
      color="neutral"
      variant="outline"
      @click="params = { name: '李四', status: '禁用', keyword: '测试' }"
    >
      外部设值
    </UButton>
  </div>
</template>

多列布局

通过 cols 属性控制网格列数:

4 列布局

2 列布局

<script setup lang="ts">
const { afz } = useAutoForm()

const schema = afz.object({
  name: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '姓名' }).optional(),
  status: afz.enum(['启用', '禁用']).meta({ label: '状态' }).optional(),
  department: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '部门' }).optional(),
  role: afz.enum(['管理员', '编辑', '查看者']).meta({ label: '角色' }).optional(),
  keyword: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '关键词' }).optional()
})

const params = ref({})
</script>

<template>
  <div class="space-y-6">
    <div class="space-y-2">
      <p class="text-sm text-muted">
        4 列布局
      </p>
      <MSearchForm v-model="params" :schema="schema" :cols="4" />
    </div>
    <div class="space-y-2">
      <p class="text-sm text-muted">
        2 列布局
      </p>
      <MSearchForm v-model="params" :schema="schema" :cols="2" />
    </div>
  </div>
</template>

折叠行为

通过 visibleRows 控制可见行数,defaultExpanded 设置默认展开状态:

2 行可见(cols=3),默认展开

<script setup lang="ts">
const { afz } = useAutoForm()

const schema = afz.object({
  name: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '姓名' }).optional(),
  status: afz.enum(['启用', '禁用', '待审核']).meta({ label: '状态' }).optional(),
  department: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '部门' }).optional(),
  role: afz.enum(['管理员', '编辑', '查看者']).meta({ label: '角色' }).optional(),
  email: afz.email({ controlProps: { placeholder: '请输入' } }).meta({ label: '邮箱' }).optional(),
  phone: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '手机号' }).optional(),
  keyword: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '关键词' }).optional()
})

const params = ref({})
</script>

<template>
  <div class="space-y-6">
    <div class="space-y-2">
      <p class="text-sm text-muted">
        2 行可见(cols=3),默认展开
      </p>
      <MSearchForm
        v-model="params"
        :schema="schema"
        :cols="3"
        :visible-rows="2"
        default-expanded
      />
    </div>
  </div>
</template>

按钮显隐

通过 showSearchButton / showResetButton 控制按钮显隐,配合 actions slot 可完全自定义按钮区域:

隐藏重置按钮

隐藏全部按钮,通过 actions slot 自定义

<script setup lang="ts">
const { afz } = useAutoForm()

const schema = afz.object({
  name: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '姓名' }).optional(),
  status: afz.enum(['启用', '禁用']).meta({ label: '状态' }).optional(),
  department: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '部门' }).optional(),
  keyword: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '关键词' }).optional()
})

const params = ref({})
</script>

<template>
  <div class="space-y-6">
    <div class="space-y-2">
      <p class="text-sm text-muted">
        隐藏重置按钮
      </p>
      <MSearchForm
        v-model="params"
        :schema="schema"
        :show-reset-button="false"
      />
    </div>
    <div class="space-y-2">
      <p class="text-sm text-muted">
        隐藏全部按钮,通过 actions slot 自定义
      </p>
      <MSearchForm
        v-model="params"
        :schema="schema"
        :show-search-button="false"
        :show-reset-button="false"
      >
        <template #actions="{ search, reset }">
          <div class="flex items-end gap-2 justify-end">
            <UButton
              color="primary"
              variant="solid"
              icon="i-lucide-filter"
              @click="search"
            >
              筛选
            </UButton>
            <UButton
              color="neutral"
              variant="ghost"
              icon="i-lucide-x"
              @click="reset"
            >
              清空
            </UButton>
          </div>
        </template>
      </MSearchForm>
    </div>
  </div>
</template>

自定义按钮

自定义按钮文本和样式:

<script setup lang="ts">
const { afz } = useAutoForm()

const schema = afz.object({
  name: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '姓名' }).optional(),
  status: afz.enum(['启用', '禁用']).meta({ label: '状态' }).optional(),
  keyword: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '关键词' }).optional(),
  department: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '部门' }).optional()
})

const params = ref({})
</script>

<template>
  <MSearchForm
    v-model="params"
    :schema="schema"
    search-text="查询"
    reset-text="清空"
    expand-text="更多"
    collapse-text="收起"
    :search-button-props="{ color: 'primary', variant: 'solid' }"
    :reset-button-props="{ color: 'error', variant: 'outline' }"
  />
</template>

响应式列数

cols 支持响应式对象,根据屏幕宽度自动调整列数:

调整浏览器窗口宽度查看效果:sm=1, md=2, lg=3, xl=4

<script setup lang="ts">
const { afz } = useAutoForm()

const schema = afz.object({
  name: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '姓名' }).optional(),
  status: afz.enum(['启用', '禁用', '待审核']).meta({ label: '状态' }).optional(),
  department: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '部门' }).optional(),
  email: afz.email({ controlProps: { placeholder: '请输入' } }).meta({ label: '邮箱' }).optional(),
  keyword: afz.string({ controlProps: { placeholder: '请输入' } }).meta({ label: '关键词' }).optional()
})

const params = ref({})
</script>

<template>
  <div class="space-y-2">
    <p class="text-sm text-muted">
      调整浏览器窗口宽度查看效果:sm=1, md=2, lg=3, xl=4
    </p>
    <MSearchForm
      v-model="params"
      :schema="schema"
      :cols="{ sm: 1, md: 2, lg: 3, xl: 4 }"
    />
  </div>
</template>

API

Props

Prop Default Type
schemaS

Zod 对象 schema,定义搜索字段

cols3number | { sm?: number; md?: number; lg?: number; xl?: number; }

网格列数

visibleRows1number

可见行数(折叠时显示的行数)

controlsAutoFormControls

自定义控件映射(复用 AutoForm 的控件系统)

globalMetaZodAutoFormFieldMeta

全局字段元数据

searchButtonPropsButtonProps

搜索按钮属性

resetButtonPropsButtonProps

重置按钮属性

searchText'搜索'string

搜索按钮文本

resetText'重置'string

重置按钮文本

icon'i-lucide-chevron-down'string

展开/收起按钮图标

expandText'展开'string

展开按钮文本

collapseText'收起'string

收起按钮文本

gap'gap-4'string

网格间距

idstring | number
stateN extends false ? Partial<InferInput<S>> : never

An object representing the current state of the form.

validate(state: Partial<InferInput<S>>) => FormError<string>[] | Promise<FormError<string>[]>

Custom validation function to validate the form state.

validateOn`['blur', 'change', 'input']`FormInputEvents[]

The list of input events that trigger the form validation.

nameN extends true ? string : never

Path of the form's state within it's parent form. Used for nesting forms. Only available if nested is true.

validateOnInputDelay`300`number

Delay in milliseconds before validating the form on input events.

transform`true`T

If true, applies schema transformations on submit.

nested`false`N

If true, this form will attach to its parent Form and validate at the same time.

onSubmit(): void | Promise<void> | (event: FormSubmitEvent<FormData<S, T>>): void | Promise<void>
acceptcharsetstring
actionstring
autocompletestring
enctypestring
methodstring
novalidatefalse | true | "true" | "false"
targetstring
modelValuePartial<InferInput<S>>
showSearchButtontrueboolean

是否显示搜索按钮

showResetButtontrueboolean

是否显示重置按钮

loadingfalseboolean

搜索按钮加载状态

defaultExpandedfalseboolean

默认展开状态

disabledboolean

Disable all inputs inside the form.

loadingAuto`true`boolean

When true, all form elements will be disabled on @submit event. This will cause any focused input elements to lose their focus state.

ui{ base?: any; }

Emits

Event Type
search[value: Partial<InferInput<S>>]
reset[]
expand[expanded: boolean]
update:modelValue[value: Partial<InferInput<S>>]

Slots

Slot Type
actions{ expanded: boolean; toggle: () => void; search: () => void; reset: () => void; loading: boolean; }

替换默认按钮区域

extraActions{ expanded: boolean; }

追加在默认按钮后面

field-default{ error?: string | boolean; } & AutoFormFieldContext<Partial<InferInput<S>>, string>
field-error{ error?: string | boolean; } & AutoFormFieldContext<Partial<InferInput<S>>, string>
field-description{ description?: string; } & AutoFormFieldContext<Partial<InferInput<S>>, string>
field-label{ label?: string; } & AutoFormFieldContext<Partial<InferInput<S>>, string>
field-help{ help?: string; } & AutoFormFieldContext<Partial<InferInput<S>>, string>
field-hint{ hint?: string; } & AutoFormFieldContext<Partial<InferInput<S>>, string>

Expose

您可以通过 useTemplateRef 访问该类型化组件实例。

NameType
formRefRef<InstanceType<typeof UForm>>

UForm 组件引用

reset()void

恢复到初始值(v-model / state prop 传入的值),并触发 reset 事件

clear()void

清空表单所有字段为空值,不触发事件

expandedRef<boolean>

当前展开/收起状态

toggle()void

切换展开/收起

Changelog

Soon
  • 66f4b — refactor(search-form): 重构 reset/clear 语义并补充 v-model 文档
  • cd443 — fix(search-form): 修复隐藏 bug 并优化组件逻辑
  • 1b12e — feat(search-form): 新增 actions 操作区及 v-model 支持
  • 22eb1 — feat(search-form): 优化展开收起按钮交互体验
  • a2b04 — feat: 新增 SearchForm 组件
Copyright © 2025 - 2026 YiXuan - MIT License