npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue3-search-form

v0.0.3

Published

基于 Vue3 + Element Plus 的配置驱动式搜索表单组件

Readme

vue3-search-form

npm license vue downloads

SearchForm 搜索表单组件

基于 Vue3 + Element Plus 的配置驱动式搜索表单组件,支持多种表单类型和自定义插槽。

在线文档 · 更新日志 · 报告问题


✨ 特性

  • 🚀 配置驱动:通过配置生成表单,无需手动编写模板
  • 📝 多种格式:支持对象和数组两种配置格式
  • 🎨 多种类型:Input、Input-Number、Select、Date、DateRange、DateTime、DateTimeRange、Slot
  • 🔘 自定义按钮:支持配置自定义操作按钮
  • 🔧 灵活扩展:支持自定义插槽组件
  • 📊 响应式数据:自动管理表单数据状态
  • 📱 栅格布局:基于 el-row/el-col 的响应式栅格系统,自动适配不同屏幕
  • 事件透明:完全透明传递所有原生事件,无内部拦截
  • 完全兼容:支持所有 Element Plus 原生属性和事件
  • 🎯 TypeScript:完整的类型定义,类型安全

📦 安装

NPM 安装

# npm
npm install vue3-search-form

# yarn
yarn add vue3-search-form

# pnpm
pnpm add vue3-search-form

🚀 快速开始

// main.ts
import { createApp } from 'vue'
import SearchForm from 'vue3-smart-form'
import 'vue3-smart-form/style.css'

const app = createApp(App)
app.use(SearchForm)

局部使用

<template>
  <SearchForm
    ref="searchFormRef"
    :form-items="formItems"
    :buttons="buttons"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import SearchForm from '/vue3-smart-form'
import { FormItemType } from '/vue3-smart-form'

const searchFormRef = ref()

const formItems = {
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字',
    props: {
      // 通过 props 配置原生事件
      onKeyup: (e) => {
        if (e.key === 'Enter') {
          handleSearch()
        }
      }
    }
  },
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: [
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 }
    ],
    props: {
      // 通过 props 配置原生事件
      onChange: (val) => {
        console.log('状态变化:', val)
      }
    }
  }
}

const buttons = [
  {
    text: '搜索',
    type: 'primary',
    icon: 'Search',
    onClick: () => {
      const formData = searchFormRef.value?.getFormData()
      handleSearch(formData)
    }
  },
  {
    text: '重置',
    icon: 'Refresh',
    onClick: () => {
      searchFormRef.value?.reset()
    }
  }
]

const handleSearch = (data) => {
  console.log('搜索参数:', data)
}
</script>

基础用法

推荐用法:使用 buttons 配置

<template>
  <SearchForm
    ref="searchFormRef"
    :form-items="formItems"
    :buttons="buttons"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const searchFormRef = ref()

const formItems = {
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字'
  },
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: [
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 }
    ]
  }
}

// 推荐:使用 buttons 配置
const buttons = [
  {
    text: '搜索',
    type: 'primary',
    icon: 'Search',
    onClick: () => {
      const formData = searchFormRef.value?.getFormData()
      console.log('搜索参数:', formData)
    }
  },
  {
    text: '重置',
    icon: 'Refresh',
    onClick: () => {
      searchFormRef.value?.reset()
    }
  },
  {
    text: '导出',
    type: 'success',
    icon: 'Download',
    onClick: () => {
      console.log('导出数据')
    }
  }
]
</script>

配置格式选择

方式一:对象格式配置

<template>
  <SearchForm
    ref="searchFormRef"
    :form-items="formItems"
    :buttons="buttons"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const searchFormRef = ref()

// 对象格式配置
const formItems = {
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字',
    props: {
      placeholder: '请输入关键字'
    }
  },
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: [
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 }
    ]
  },
  dateRange: {
    type: FormItemType.DATE_RANGE,
    label: '创建时间'
  }
}

const buttons = [
  {
    text: '查询',
    type: 'primary',
    onClick: () => {
      const data = searchFormRef.value?.getFormData()
      console.log('搜索参数:', data)
    }
  }
]
</script>

方式二:数组格式配置

<template>
  <SearchForm
    ref="searchFormRef"
    :form-items="formItems"
    :buttons="buttons"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const searchFormRef = ref()

// 数组格式配置(每个项必须包含 key 字段)
const formItems = [
  {
    key: 'keyword',
    type: FormItemType.INPUT,
    label: '关键字',
    props: {
      placeholder: '请输入关键字'
    }
  },
  {
    key: 'status',
    type: FormItemType.SELECT,
    label: '状态',
    options: [
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 }
    ]
  },
  {
    key: 'dateRange',
    type: FormItemType.DATE_RANGE,
    label: '创建时间'
  }
]

const buttons = [
  {
    text: '查询',
    type: 'primary',
    onClick: () => {
      const data = searchFormRef.value?.getFormData()
      console.log('搜索参数:', data)
    }
  }
]
</script>

API

Props

| 参数名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | formItems | FormConfig | - | 表单项配置(支持对象或数组格式,必填) | | labelWidth | string | '80px' | 表单标签宽度 | | defaultCol | ColConfig | { xs: 24, sm: 12, md: 8, lg: 6, xl: 6 } | 默认栅格配置,应用于所有未单独配置 col 的表单项 | | inline | boolean | false | 是否行内表单(true 时不使用栅格布局) | | formStyle | object | - | 表单样式 | | formClass | string | - | 表单类名 | | buttons | ButtonConfig[] | [] | 按钮配置(配置形式) | | formProps | object | {} | el-form 原生属性 |

💡 设计理念:完全配置驱动,表单项和按钮均通过配置管理,保持 API 简洁统一。

栅格配置 (ColConfig)

| 参数名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | span | number | - | 默认占比 (1-24) | | xs | number | 24 | <768px 时的占比 | | sm | number | 12 | ≥768px 时的占比 | | md | number | 8 | ≥992px 时的占比 | | lg | number | 6 | ≥1200px 时的占比 | | xl | number | 6 | ≥1920px 时的占比 |

Events

| 事件名 | 参数 | 说明 | |--------|------|------| | reset | - | 点击重置按钮触发(内部调用)或手动调用 reset 方法时触发 |

Expose

| 方法名 | 参数 | 返回值 | 说明 | |--------|------|--------|------| | getFormData | - | FormData | 获取当前表单数据 | | reset | - | - | 手动触发重置(并触发 reset 事件) | | setFieldValue | (key: string, value: any) | - | 设置单个字段的值 | | setFieldsValue | (values: Record<string, any>) | - | 批量设置字段的值 | | formRef | - | FormInstance | Element Plus 表单实例引用 |

表单项配置 (FormItemConfig)

| 参数名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | key | string | - | 字段名(必填) | | type | FormItemType | - | 表单项类型(必填) | | label | string | - | 标签文本 | | show | boolean | true | 是否显示 | | value | any | - | 默认值 | | col | ColConfig | - | 栅格配置,不传则使用全局 defaultCol | | options | array \| function | - | 下拉选项(仅 select) | | optionsRef | string | - | 选项数组来源的响应式数据引用(仅 select) | | component | Component \| { render: () => VNode } | - | 自定义渲染组件(仅 slot) | | props | object | - | 表单组件原生属性和事件(placeholder, clearable, filterable, format, valueFormat, style, class, onKeyup, onChange 等) | | itemProps | object | - | el-form-item 原生属性(包含 style, class, labelWidth 等) |

💡 设计理念:只保留真正的元数据作为顶级属性,所有原生组件属性(placeholder、clearable、filterable、format、valueFormat、style、class 等)都通过 props 配置,保持 API 简洁统一。

表单项类型 (FormItemType)

| 类型 | 值 | 说明 | |------|------|------| | INPUT | 'input' | 输入框 | | INPUT_NUMBER | 'inputNumber' | 数字输入框 | | SELECT | 'select' | 选择器 | | DATE | 'date' | 日期选择器 | | DATE_RANGE | 'dateRange' | 日期范围选择器 | | DATETIME | 'datetime' | 日期时间选择器 | | DATETIME_RANGE | 'datetimeRange' | 日期时间范围选择器 | | SLOT | 'slot' | 自定义插槽 |

按钮配置说明 (ButtonConfig)

| 参数名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | text | string | - | 按钮文本(必填) | | type | string | 'default' | 按钮类型:primary/success/warning/danger/info/text/default | | icon | string | - | 按钮图标 | | show | boolean | true | 是否显示 | | props | object | - | Element Plus 按钮原生属性 | | onClick | function | - | 点击事件处理函数 |

使用示例

1. 基础输入框

{
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字',
    props: {
      placeholder: '请输入关键字'
    }
  }
}

2. 数字输入框

{
  age: {
    type: FormItemType.INPUT_NUMBER,
    label: '年龄',
    props: {
      min: 0,
      max: 120,
      step: 1,
      precision: 0
    }
  },
  price: {
    type: FormItemType.INPUT_NUMBER,
    label: '价格',
    props: {
      min: 0,
      precision: 2,
      step: 0.1,
      controlsPosition: 'right'
    }
  }
}

3. 下拉选择器(静态选项)

{
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: [
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 }
    ],
    props: {
      placeholder: '请选择状态',
      clearable: true,
      filterable: true
    }
  }
}

4. 下拉选择器(动态选项)

const statusOptions = ref([
  { label: '启用', value: 1 },
  { label: '禁用', value: 0 }
])

{
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: () => statusOptions.value
  }
}

5. 日期选择器

{
  createDate: {
    type: FormItemType.DATE,
    label: '创建日期',
    props: {
      format: 'YYYY-MM-DD',
      valueFormat: 'YYYY-MM-DD'
    }
  }
}

6. 日期范围选择器

{
  dateRange: {
    type: FormItemType.DATE_RANGE,
    label: '创建时间',
    props: {
      startPlaceholder: '开始日期',
      endPlaceholder: '结束日期'
    }
  }
}

7. 日期时间选择器

{
  createTime: {
    type: FormItemType.DATETIME,
    label: '创建时间',
    props: {
      format: 'YYYY-MM-DD HH:mm:ss',
      valueFormat: 'YYYY-MM-DD HH:mm:ss'
    }
  }
}

8. 日期时间范围选择器

{
  datetimeRange: {
    type: FormItemType.DATETIME_RANGE,
    label: '时间范围',
    props: {
      format: 'YYYY-MM-DD HH:mm:ss',
      valueFormat: 'YYYY-MM-DD HH:mm:ss'
    }
  }
}

9. 自定义插槽

<template>
  <SearchForm :form-items="formItems">
    <template #customSlot="{ field, value, config }">
      <el-input v-model="formData[field]" placeholder="自定义内容" />
    </template>
  </SearchForm>
</template>

<script setup lang="ts">
import { reactive } from 'vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const formData = reactive({})

const formItems = {
  customSlot: {
    type: FormItemType.SLOT,
    label: '自定义字段'
  }
}
</script>

10. 事件透明传逩

组件完全透明传递所有原生事件,通过 props 配置即可监听任何原生事件。

const formItems = {
  // Input 回车事件
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字',
    props: {
      onKeyup: (e) => {
        if (e.key === 'Enter') {
          console.log('回车键按下')
          // 执行搜索逻辑
        }
      },
      onChange: (value) => {
        console.log('值变化:', value)
      },
      onFocus: () => {
        console.log('获得焦点')
      },
      onBlur: () => {
        console.log('失去焦点')
      }
    }
  },

  // InputNumber 变化事件
  price: {
    type: FormItemType.INPUT_NUMBER,
    label: '价格',
    props: {
      onChange: (value) => {
        console.log('价格变化:', value)
      }
    }
  },

  // Select 变化事件
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: statusOptions,
    props: {
      onChange: (value) => {
        console.log('状态变化:', value)
        // 执行联动逻辑或搜索
      },
      onVisibleChange: (visible) => {
        console.log('下拉框显示状态:', visible)
      }
    }
  },

  // DatePicker 事件
  dateRange: {
    type: FormItemType.DATE_RANGE,
    label: '日期范围',
    props: {
      onCalendarChange: (dates) => {
        console.log('日历变化:', dates)
      },
      onVisibleChange: (visible) => {
        console.log('选择器显示状态:', visible)
      }
    }
  }
}

重要说明:

  • 组件不会拦截或处理任何原生事件
  • 所有事件通过 props 配置直接传递给原生组件
  • 事件处理函数由用户完全控制
  • 可以在事件处理函数中调用 getFormData() 获取表单数据

11. 完整示例

<template>
  <div class="page-container">
    <SearchForm
      ref="searchFormRef"
      :form-items="formItems"
      label-width="100px"
    >
      <!-- 自定义插槽 -->
      <template #customField="{ field, value }">
        <el-cascader
          v-model="formData[field]"
          :options="cascaderOptions"
          clearable
        />
      </template>
    </SearchForm>

    <!-- 表格区域 -->
    <el-table :data="tableData">
      <!-- 表格列 -->
    </el-table>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const searchFormRef = ref()
const formData = reactive({})
const tableData = ref([])

const formItems = {
  // 用户名输入框
  username: {
    type: FormItemType.INPUT,
    label: '用户名',
    props: {
      placeholder: '请输入用户名',
      maxlength: 20,
      onKeyup: (e) => {
        if (e.key === 'Enter') {
          handleSearch()
        }
      }
    }
  },

  // 状态下拉框
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: [
      { label: '全部', value: '' },
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 }
    ],
    props: {
      placeholder: '请选择状态',
      clearable: true,
      filterable: true
    }
  },

  // 创建时间范围
  createTime: {
    type: FormItemType.DATE_RANGE,
    label: '创建时间',
    props: {
      startPlaceholder: '开始日期',
      endPlaceholder: '结束日期',
      unlinkPanels: true
    }
  },

  // 自定义插槽字段
  customField: {
    type: FormItemType.SLOT,
    label: '级联选择'
  }
}

// 处理搜索
const handleSearch = () => {
  const data = searchFormRef.value?.getFormData()
  console.log('搜索参数:', data)
  // 调用 API 获取数据
  fetchData(data)
}

// 获取表单数据
const getFormData = () => {
  const data = searchFormRef.value?.getFormData()
  console.log('当前表单数据:', data)
  return data
}

// 获取表格数据
const fetchData = async (params) => {
  // API 调用
}
</script>

高级用法

栅格布局

组件默认使用 el-row + el-col 栅格系统,自动适配不同屏幕尺寸。

<template>
  <!-- 使用默认栅格配置:小屏1个/行,中屏3个,大屏4个 -->
  <SearchForm
    :form-items="formItems"
    :buttons="buttons"
  />

  <!-- 自定义全局栅格配置 -->
  <SearchForm
    :form-items="formItems"
    :default-col="{ xs: 24, sm: 12, md: 12, lg: 8, xl: 6 }"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import SearchForm from 'vue3-smart-form'
import { FormItemType } from 'vue3-smart-form'

const searchFormRef = ref()

const formItems = [
  { key: 'keyword', type: FormItemType.INPUT, label: '关键字' },
  { key: 'status', type: FormItemType.SELECT, label: '状态', options: [...] },
  // 某个字段需要占更宽,单独配置 col
  { 
    key: 'remark', 
    type: FormItemType.INPUT, 
    label: '备注',
    col: { xs: 24, sm: 24, md: 12, lg: 12 }  // 占两列宽度
  },
  // 日期范围通常需要更宽
  {
    key: 'dateRange',
    type: FormItemType.DATE_RANGE,
    label: '创建时间',
    col: { xs: 24, sm: 24, md: 12, lg: 8 }
  }
]

const buttons = [
  {
    text: '搜索',
    type: 'primary',
    onClick: () => console.log(searchFormRef.value?.getFormData())
  },
  {
    text: '重置',
    onClick: () => searchFormRef.value?.reset()
  }
]
</script>

栅格断点说明:

  • xs: <768px(手机)
  • sm: ≥768px(平板)
  • md: ≥992px(小屏桌面)
  • lg: ≥1200px(桌面)
  • xl: ≥1920px(大屏)

动态控制表单项显示

const formItems = computed(() => ({
  username: {
    type: FormItemType.INPUT,
    label: '用户名'
  },
  // 根据条件动态显示
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    show: someCondition.value, // 动态控制显示
    options: statusOptions.value
  }
}))

联动效果

<script setup lang="ts">
const searchFormRef = ref()

const formItems = {
  appId: {
    type: FormItemType.SELECT,
    label: '应用',
    options: appOptions,
    props: {
      onChange: (value) => {
        handleAppChange(value)
      }
    }
  },
  topicId: {
    type: FormItemType.SELECT,
    label: '话题',
    options: [], // 初始为空
    show: false // 初始隐藏
  }
}

// 监听字段变化,实现联动
const handleAppChange = (value) => {
  // 根据 appId 加载对应的话题列表
  loadTopicsByApp(value)
  // 显示话题选择器
  formItems.topicId.show = !!value
}
</script>

编程式设置表单值

<script setup lang="ts">
const searchFormRef = ref()

// 设置单个字段
const setKeyword = () => {
  searchFormRef.value?.setFieldValue('keyword', '默认关键字')
}

// 批量设置字段
const setDefaultValues = () => {
  searchFormRef.value?.setFieldsValue({
    keyword: '测试',
    status: 1,
    dateRange: ['2024-01-01', '2024-12-31']
  })
}

// 从 URL 参数回填表单
onMounted(() => {
  const query = route.query
  if (Object.keys(query).length > 0) {
    searchFormRef.value?.setFieldsValue(query)
  }
})
</script>

自定义样式

{
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字',
    props: {
      style: {
        width: '300px'
      },
      class: 'custom-keyword'
    }
  }
}

原生属性和事件支持

组件完全支持 Element Plus 原生属性和事件:

1. el-form 原生属性(通过 formProps 配置)

<SearchForm
  :form-items="formItems"
  :form-props="{
    labelPosition: 'right',
    size: 'large',
    disabled: false,
    validateOnRuleChange: true
  }"
> </SearchForm>

2. el-form-item 原生属性(通过 itemProps 配置)

const formItems = {
  username: {
    type: FormItemType.INPUT,
    label: '用户名',
    itemProps: {
      required: true,
      rules: [
        { required: true, message: '请输入用户名', trigger: 'blur' },
        { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
      ],
      error: '错误信息',
      showMessage: true,
      inlineMessage: false
    }
  }
}

3. 表单组件原生属性(通过 props 配置)

{
  // Input 所有原生属性和事件
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字',
    props: {
      maxlength: 20,
      showWordLimit: true,
      clearable: true,
      onKeyup: (e) => {
        if (e.key === 'Enter') handleSearch()
      }
    }
  },

  // Select 所有原生属性和事件
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: [...],
    props: {
      multiple: true,
      filterable: true,
      onChange: (val) => console.log('变化', val)
    }
  },

  // DatePicker 所有原生属性和事件
  dateRange: {
    type: FormItemType.DATE_RANGE,
    label: '时间范围',
    props: {
      unlinkPanels: true,
      shortcuts: [
        {
          text: '最近一周',
          value: () => [new Date(Date.now() - 7 * 24 * 3600 * 1000), new Date()]
        }
      ]
    }
  }
}

自定义按钮

组件支持两种方式添加自定义按钮:配置形式和插槽形式。

方式一:配置形式(推荐)

适合简单的按钮场景,配置集中,代码简洁:

<template>
  <SearchForm
    ref="searchFormRef"
    :form-items="formItems"
    :buttons="customButtons"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const searchFormRef = ref()

const formItems = {
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字'
  },
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: [
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 }
    ]
  }
}

// 自定义按钮配置
const customButtons = [
  {
    text: '导出',
    type: 'success',
    icon: 'Download',
    show: true,
    onClick: () => {
      ElMessage.success('导出数据')
      exportData()
    }
  },
  {
    text: '批量删除',
    type: 'danger',
    icon: 'Delete',
    show: true,
    props: {
      disabled: false
    },
    onClick: () => {
      ElMessage.warning('批量删除')
      batchDelete()
    }
  },
  {
    text: '刷新',
    type: 'info',
    icon: 'Refresh',
    onClick: () => {
      const data = searchFormRef.value?.getFormData()
      console.log('查询:', data)
    }
  }
]

const exportData = () => {
  // 导出逻辑
}

const batchDelete = () => {
  // 批量删除逻辑
}
</script>

方式二:插槽形式

适合复杂的按钮场景,如按钮组、下拉菜单等:

<template>
  <SearchForm
    :form-items="formItems"
  >
    <!-- 按钮插槽 -->
    <template #buttons>
      <el-button type="success" :icon="Download" @click="exportData">
        导出
      </el-button>

      <el-dropdown @command="handleDropdownCommand">
        <el-button type="primary">
          更多操作 <el-icon><arrow-down /></el-icon>
        </el-button>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item command="batchEdit">批量编辑</el-dropdown-item>
            <el-dropdown-item command="batchDelete">批量删除</el-dropdown-item>
            <el-dropdown-item command="refresh">刷新</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>

      <el-button-group>
        <el-button :icon="Plus" @click="handleAdd">新增</el-button>
        <el-button :icon="Upload" @click="handleUpload">上传</el-button>
      </el-button-group>
    </template>
  </SearchForm>
</template>

<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { Download, Plus, Upload, ArrowDown } from '@element-plus/icons-vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const formItems = {
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字'
  }
}

const exportData = () => {
  ElMessage.success('导出数据')
}

const handleDropdownCommand = (command) => {
  switch (command) {
    case 'batchEdit':
      ElMessage.info('批量编辑')
      break
    case 'batchDelete':
      ElMessage.warning('批量删除')
      break
    case 'refresh':
      ElMessage.success('刷新')
      break
  }
}

const handleAdd = () => {
  ElMessage.success('新增')
}

const handleUpload = () => {
  ElMessage.info('上传')
}
</script>

方式三:混合使用

同时使用配置和插槽,配置按钮在前,插槽按钮在后:

<template>
  <SearchForm
    ref="searchFormRef"
    :form-items="formItems"
    :buttons="configButtons"
  >
    <!-- 插槽按钮会显示在配置按钮之后 -->
    <template #buttons>
      <el-button type="primary" :icon="Plus" @click="handleAdd">
        新增
      </el-button>
      <el-dropdown @command="handleMoreCommand">
        <el-button>
          更多 <el-icon><arrow-down /></el-icon>
        </el-button>
        <template #dropdown>
          <el-dropdown-menu>
            <el-dropdown-item command="export">导出</el-dropdown-item>
            <el-dropdown-item command="import">导入</el-dropdown-item>
          </el-dropdown-menu>
        </template>
      </el-dropdown>
    </template>
  </SearchForm>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ArrowDown, Plus } from '@element-plus/icons-vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const searchFormRef = ref()

// 配置按钮(查询、重置等基础操作)
const configButtons = [
  {
    text: '查询',
    type: 'primary',
    icon: 'Search',
    onClick: () => {
      const data = searchFormRef.value?.getFormData()
      console.log('查询:', data)
    }
  },
  {
    text: '重置',
    icon: 'Refresh',
    onClick: () => {
      searchFormRef.value?.reset()
    }
  }
]

const formItems = {
  keyword: {
    type: FormItemType.INPUT,
    label: '关键字'
  }
}

const handleAdd = () => {
  console.log('新增')
}

const handleMoreCommand = (command) => {
  console.log('更多操作:', command)
}
</script>

数组格式配置 + 自定义按钮

<template>
  <SearchForm
    ref="searchFormRef"
    :form-items="formItems"
    :buttons="buttons"
  >
    <template #customSlot="{ field, value }">
      <el-input v-model="customValues[field]" placeholder="自定义内容" />
    </template>
  </SearchForm>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const searchFormRef = ref()
const customValues = reactive({})

// 数组格式配置
const formItems = [
  {
    key: 'username',
    type: FormItemType.INPUT,
    label: '用户名'
  },
  {
    key: 'status',
    type: FormItemType.SELECT,
    label: '状态',
    options: [
      { label: '全部', value: '' },
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 }
    ]
  },
  {
    key: 'customSlot',
    type: FormItemType.SLOT,
    label: '自定义'
  }
]

// 自定义按钮(替代默认的搜索和重置按钮)
const buttons = [
  {
    text: '查询',
    type: 'primary',
    icon: 'Search',
    onClick: () => {
      const data = searchFormRef.value?.getFormData()
      console.log('查询数据:', data)
    }
  },
  {
    text: '重置',
    icon: 'Refresh',
    onClick: () => {
      searchFormRef.value?.reset()
    }
  },
  {
    text: '新增',
    type: 'success',
    icon: 'Plus',
    onClick: () => {
      // 新增逻辑
    }
  }
]
</script>

实战应用场景

场景一:用户管理页面的搜索表单

典型的后台管理系统用户列表搜索,包含用户名、状态、角色、注册时间等条件。

<template>
  <div class="user-management">
    <SearchForm
      ref="searchFormRef"
      :form-items="formItems"
      :buttons="buttons"
      label-width="90px"
    />

    <el-table :data="userList" v-loading="loading">
      <el-table-column prop="username" label="用户名" />
      <el-table-column prop="email" label="邮箱" />
      <el-table-column prop="roleName" label="角色" />
      <el-table-column prop="status" label="状态">
        <template #default="{ row }">
          <el-tag :type="row.status === 1 ? 'success' : 'danger'">
            {{ row.status === 1 ? '启用' : '禁用' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="createTime" label="注册时间" />
    </el-table>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'

const searchFormRef = ref()
const loading = ref(false)
const userList = ref([])

// 角色选项
const roleOptions = ref([
  { label: '管理员', value: 'admin' },
  { label: '编辑', value: 'editor' },
  { label: '普通用户', value: 'user' }
])

// 表单配置
const formItems = {
  username: {
    type: FormItemType.INPUT,
    label: '用户名',
    props: {
      placeholder: '请输入用户名',
      maxlength: 20,
      clearable: true
    }
  },
  email: {
    type: FormItemType.INPUT,
    label: '邮箱',
    props: {
      placeholder: '请输入邮箱'
    }
  },
  role: {
    type: FormItemType.SELECT,
    label: '角色',
    options: () => roleOptions.value,
    props: {
      placeholder: '请选择角色',
      clearable: true,
      filterable: true
    }
  },
  status: {
    type: FormItemType.SELECT,
    label: '状态',
    options: [
      { label: '全部', value: '' },
      { label: '启用', value: 1 },
      { label: '禁用', value: 0 }
    ],
    props: {
      placeholder: '请选择状态',
      clearable: true
    }
  },
  registerTime: {
    type: FormItemType.DATE_RANGE,
    label: '注册时间',
    props: {
      startPlaceholder: '开始日期',
      endPlaceholder: '结束日期',
      unlinkPanels: true
    }
  }
}

// 按钮配置
const buttons = [
  {
    text: '查询',
    type: 'primary',
    icon: 'Search',
    onClick: () => {
      const formData = searchFormRef.value?.getFormData()
      fetchUserList(formData)
    }
  },
  {
    text: '重置',
    icon: 'Refresh',
    onClick: () => searchFormRef.value?.reset()
  },
  {
    text: '新增用户',
    type: 'success',
    icon: 'Plus',
    onClick: () => ElMessage.info('打开新增对话框')
  },
  {
    text: '导出',
    type: 'warning',
    icon: 'Download',
    onClick: () => ElMessage.success('导出用户列表')
  }
]

// 获取用户列表
const fetchUserList = async (params: any) => {
  loading.value = true
  try {
    const res = await getUserList(params)
    userList.value = res.data.list
  } catch (error) {
    ElMessage.error('获取用户列表失败')
  } finally {
    loading.value = false
  }
}
</script>

场景二:订单管理页面的复杂搜索

包含多个筛选条件、级联选择、自定义插槽等复杂场景。

<template>
  <div class="order-management">
    <SearchForm
      ref="searchFormRef"
      :form-items="formItems"
      :buttons="buttons"
      label-width="100px"
    >
      <!-- 自定义区域级联选择 -->
      <template #region="{ field }">
        <el-cascader
          v-model="customFormData[field]"
          :options="regionOptions"
          :props="{
            value: 'code',
            label: 'name',
            children: 'children'
          }"
          clearable
          placeholder="请选择地区"
          style="width: 200px"
        />
      </template>

      <!-- 自定义商品选择 -->
      <template #product="{ field }">
        <el-select
          v-model="customFormData[field]"
          filterable
          remote
          reserve-keyword
          placeholder="请输入商品名称"
          :remote-method="searchProducts"
          :loading="productLoading"
          clearable
        >
          <el-option
            v-for="item in productOptions"
            :key="item.id"
            :label="item.name"
            :value="item.id"
          />
        </el-select>
      </template>
    </SearchForm>

    <el-table :data="orderList">
      <!-- 订单列表列 -->
    </el-table>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import SearchForm from '@/components/SearchForm/index.vue'
import { FormItemType } from '@/components/SearchForm/types'
import { getRegionList, searchProducts } from '@/api/common'

const searchFormRef = ref()
const customFormData = reactive({})
const orderList = ref([])
const productLoading = ref(false)
const productOptions = ref([])

// 地区选项
const regionOptions = ref([])
loadRegionOptions()

async function loadRegionOptions() {
  const res = await getRegionList()
  regionOptions.value = res.data
}

// 表单配置(数组格式)
const formItems = [
  {
    key: 'orderNo',
    type: FormItemType.INPUT,
    label: '订单号',
    props: {
      placeholder: '请输入订单号'
    }
  },
  {
    key: 'region',
    type: FormItemType.SLOT,
    label: '配送地区'
  },
  {
    key: 'product',
    type: FormItemType.SLOT,
    label: '商品名称'
  },
  {
    key: 'orderStatus',
    type: FormItemType.SELECT,
    label: '订单状态',
    options: [
      { label: '全部', value: '' },
      { label: '待付款', value: 0 },
      { label: '待发货', value: 1 },
      { label: '已发货', value: 2 },
      { label: '已完成', value: 3 },
      { label: '已取消', value: 4 }
    ],
    props: {
      clearable: true
    }
  },
  {
    key: 'payType',
    type: FormItemType.SELECT,
    label: '支付方式',
    options: [
      { label: '全部', value: '' },
      { label: '微信支付', value: 'wechat' },
      { label: '支付宝', value: 'alipay' },
      { label: '银行卡', value: 'bank' }
    ],
    props: {
      clearable: true
    }
  },
  {
    key: 'orderTime',
    type: FormItemType.DATETIME_RANGE,
    label: '下单时间',
    props: {
      format: 'YYYY-MM-DD HH:mm:ss',
      valueFormat: 'YYYY-MM-DD HH:mm:ss',
      startPlaceholder: '开始时间',
      endPlaceholder: '结束时间'
    }
  }
]

// 按钮配置
const buttons = [
  {
    text: '查询',
    type: 'primary',
    icon: 'Search',
    onClick: () => {
      const formData = searchFormRef.value?.getFormData()
      fetchOrderList(formData)
    }
  },
  {
    text: '重置',
    icon: 'Refresh',
    onClick: () => searchFormRef.value?.reset()
  },
  {
    text: '批量导出',
    type: 'success',
    icon: 'Download',
    onClick: () => console.log('批量导出')
  },
  {
    text: '批量发货',
    type: 'warning',
    icon: 'Van',
    onClick: () => console.log('批量发货')
  }
]

// 搜索商品
async function searchProducts(query: string) {
  if (!query) return
  productLoading.value = true
  const res = await searchProducts({ keyword: query })
  productOptions.value = res.data
  productLoading.value = false
}

// 获取订单列表
async function fetchOrderList(params: any) {
  // 合并自定义表单数据
  const queryParams = { ...params, ...customFormData }
  console.log('查询参数:', queryParams)
  // 调用 API
}
</script>

注意事项

  1. 配置格式选择

    • 对象格式:适合配置项较少且固定的场景,key 作为对象属性名
    • 数组格式:适合配置项较多或需要动态调整顺序的场景,每个项必须包含 key 字段
  2. 表单项 key 唯一性:确保每个表单项的 key 是唯一的

  3. 插槽命名:使用插槽时,插槽名称应与表单项 key 保持一致

  4. 默认值类型:日期范围类型的默认值为数组 [],其他类型默认值为空字符串 ''

  5. 响应式数据:表单数据是响应式的,可以直接通过 ref 获取和修改

  6. 类型安全:项目中已提供完整的 TypeScript 类型定义,建议开启类型检查

  7. 按钮配置方式选择

    • 配置形式(推荐):适合简单按钮,配置集中,代码简洁
    • 插槽形式:适合复杂按钮(如下拉菜单、按钮组),更灵活
    • 混合使用:简单按钮用配置,复杂按钮用插槽,两者共存
    • 按钮显示顺序:配置按钮 → 插槽按钮
  8. 配置驱动理念:组件完全遵循配置驱动设计,表单项和按钮均通过配置/插槽管理,不提供额外的开关属性

完整类型定义

详细类型定义请查看 types.ts 文件。


📄 License

MIT

🤝 贡献

欢迎贡献代码、提出建议或报告问题!

  1. Fork 本仓库
  2. 创建特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交更改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启 Pull Request

💖 支持

如果这个组件对你有帮助,请给它一个 ⭐️

📮 联系方式


Made with ❤️ by chenx18

⬆ 返回顶部